Not
Åtkomst till denna sida kräver auktorisation. Du kan prova att logga in eller byta katalog.
Åtkomst till denna sida kräver auktorisation. Du kan prova att byta katalog.
Som beskrivs i översikten är ett grundläggande beslut du behöver fatta om dina tester kommer att omfatta ditt produktionsdatabassystem – precis som programmet gör – eller om dina tester körs mot en testdubblett, vilket ersätter ditt produktionsdatabassystem.
Testning mot en verklig extern resurs – i stället för att ersätta den med en testdubblett – kan innebära följande svårigheter:
- I många fall är det helt enkelt inte möjligt eller praktiskt att testa mot den faktiska externa resursen. Ditt program kan till exempel interagera med en tjänst som inte enkelt kan testas mot (på grund av hastighetsbegränsning eller avsaknaden av en testmiljö).
- Även om det är möjligt att ta med den verkliga externa resursen kan det vara mycket långsamt: det kan ta för lång tid att köra en stor mängd tester mot en molntjänst. Testning bör ingå i utvecklarens dagliga arbetsflöde, så det är viktigt att testerna körs snabbt.
- Att köra tester mot en extern resurs kan innebära isoleringsproblem, där tester stör varandra. Flera tester som körs parallellt mot en databas kan till exempel ändra data och orsaka att varandra misslyckas på olika sätt. Om du använder en testdubblett undviker du detta eftersom varje test körs mot en egen minnesintern resurs och därför är naturligt isolerad från andra tester.
Tester som skickas mot en testdubblett garanterar dock inte att programmet fungerar när det körs mot den verkliga externa resursen. Till exempel kan en databastestdubblett utföra skiftlägeskänsliga strängjämförelser, medan produktionsdatabassystemet gör skiftlägesokänsliga jämförelser. Sådana problem upptäcks endast när tester körs mot din verkliga produktionsdatabas, vilket gör dessa tester till en viktig del av alla teststrategier.
Det kan vara enklare att testa mot databasen än det verkar
På grund av ovanstående svårigheter med att testa mot en riktig databas uppmanas utvecklare ofta att använda testdubblar först och har en robust testsvit som de kan köra ofta på sina datorer. tester som involverar databasen ska däremot utföras mycket mindre ofta och i många fall också ge mycket mindre täckning. Vi rekommenderar att du tänker mer på det senare och föreslår att databaser faktiskt kan påverkas mycket mindre av ovanstående problem än vad folk brukar tro:
- De flesta databaser kan numera enkelt installeras på utvecklarens dator. Containerbaserade tekniker som Docker kan göra detta mycket enkelt, och bibliotek som Testcontainers kan hjälpa till att automatisera livscykeln för containerbaserade databaser i dina tester. Tekniker som GitHub-arbetsytor och Dev Container konfigurerar hela utvecklingsmiljön åt dig (inklusive databasen). När du använder SQL Server är det också möjligt att testa mot LocalDB i Windows eller enkelt konfigurera en Docker-avbildning i Linux.
- Testningen mot en lokal databas – med en rimlig testdatauppsättning – är vanligtvis extremt snabb: kommunikationen är helt lokal och testdata buffrade vanligtvis i minnet på databassidan. SJÄLVA EF Core innehåller över 30 000 tester enbart mot SQL Server. dessa slutförs på ett tillförlitligt sätt på några minuter, körs i CI vid varje enskild incheckning och körs mycket ofta av utvecklare lokalt. Vissa utvecklare vänder sig till en minnesintern databas (en "falsk") i tron att detta behövs för hastighet - detta är nästan aldrig fallet.
- Isolering är verkligen ett hinder när du kör tester mot en verklig databas, eftersom tester kan ändra data och störa varandra. Det finns dock olika tekniker för att tillhandahålla isolering i scenarier för databastestning. vi koncentrerar oss på dessa i Testning mot ditt produktionsdatabassystem).
Ovanstående är inte avsett att nedvärdera testdubblar eller argumentera mot att använda dem. För det första är testdubblar nödvändiga för vissa scenarier som inte kan testas annars, till exempel att simulera databasfel. Men enligt vår erfarenhet drar sig användare ofta för att testa mot sin databas av ovanstående skäl och tror att det är långsamt, svårt eller opålitligt, när så inte nödvändigtvis är fallet. Testning mot ditt produktionsdatabassystem syftar till att åtgärda detta genom att tillhandahålla riktlinjer och exempel för att skriva snabba, isolerade tester mot din databas.
Olika typer av testdubblar
Testdubblar är en bred term som omfattar väldigt olika metoder. Det här avsnittet beskriver några vanliga tekniker som omfattar testdubblar för att testa EF Core-program:
- Använd SQLite (minnesinternt läge) som en falsk databas och ersätt ditt produktionsdatabassystem.
- Använd EF Core-in-memory-providern som en falsk databas och ersätt ditt produktionsdatabassystem.
- Håna eller stub ut
DbContextochDbSet. - Introducera ett lagringslager mellan EF Core och din programkod och simulera eller stub det lagret.
Nedan ska vi utforska vad varje metod betyder och jämföra den med de andra. Vi rekommenderar att du läser igenom de olika metoderna för att få en fullständig förståelse för var och en. Om du har bestämt dig för att skriva tester som inte omfattar ditt produktionsdatabassystem är ett lagringslager den enda metoden som tillåter omfattande och tillförlitlig stubbning/modellering av datalagret. Den metoden har dock en betydande kostnad när det gäller implementering och underhåll.
SQLite som en simulerad databas
En möjlig testmetod är att byta ut din produktionsdatabas (t.ex. SQL Server) mot SQLite och effektivt använda den som ett test-"fake". Förutom enkel installation har SQLite en minnesintern databasfunktion som är särskilt användbar för testning: varje test är naturligt isolerat i en egen minnesintern databas och inga faktiska filer behöver hanteras.
Innan du gör det är det dock viktigt att förstå att olika databasprovidrar i EF Core beter sig annorlunda – EF Core försöker inte abstrahera alla aspekter av det underliggande databassystemet. I grunden innebär det att testning mot SQLite inte garanterar samma resultat som mot SQL Server eller någon annan databas. Här följer några exempel på möjliga beteendeskillnader:
- Samma LINQ-fråga kan returnera olika resultat på olika leverantörer. SQL Server gör till exempel skiftlägesokänslig strängjämförelse som standard, medan SQLite gör skiftlägeskänslig jämförelse. Detta kan göra att dina tester klarar sig mot SQLite där de skulle ha misslyckats mot SQL Server (eller vice versa).
- Vissa frågor som fungerar på SQL Server stöds helt enkelt inte på SQLite, eftersom det exakta SQL-stödet i dessa två databaser skiljer sig åt.
- Om frågan råkar använda en providerspecifik metod, till exempel SQL Server,
EF.Functions.DateDiffDaymisslyckas den frågan på SQLite och kan inte testas. - Rå SQL kan fungera, eller så kan det misslyckas eller returnera olika resultat, beroende på exakt vad som görs. SQL-dialekter skiljer sig på många sätt mellan databaser.
Jämfört med att köra tester mot produktionsdatabassystemet är det relativt enkelt att komma igång med SQLite, och så många användare gör det. Tyvärr tenderar ovanstående begränsningar att bli problematiska när du testar EF Core-program, även om de inte verkar vara i början. Därför rekommenderar vi att du antingen skriver dina tester mot din verkliga databas, eller om det är absolut nödvändigt att använda testdubbletter, acceptera kostnaden för ett referensmönster enligt beskrivningen nedan.
Information om hur du använder SQLite för testning finns i det här avsnittet.
I minnet som en databasmockup
Som ett alternativ till SQLite levereras EF Core också med en minnesintern provider. Trots att denna provider ursprungligen utformades för att stödja intern testning av EF Core, väljer vissa utvecklare att använda den som en falsk databas när de testar program som baseras på EF Core. Detta rekommenderas inte: som en falsk databas har minnesinternt problem samma problem som SQLite (se ovan), men har dessutom följande ytterligare begränsningar:
- Den minnesinterna providern stöder vanligtvis färre frågetyper än SQLite-providern, eftersom den inte är en relationsdatabas. Fler frågor misslyckas eller fungerar annorlunda jämfört med din produktionsdatabas.
- Transaktioner stöds inte.
- Raw SQL stöds inte helt. Jämför detta med SQLite, där det är möjligt att använda rå SQL, så länge SQL fungerar på samma sätt på SQLite och din produktionsdatabas.
- Den minnesinterna providern har inte optimerats för prestanda och fungerar vanligtvis långsammare än SQLite i minnesinternt läge (eller till och med ditt produktionsdatabassystem).
Sammanfattningsvis har i-minnet alla nackdelar med SQLite, samt några fler, och ger inga fördelar i gengäld. Om du letar efter en enkel, imiterad minnesdatabas använder du SQLite istället för den minnesinterna leverantören, men överväg att använda repositoriemönstret enligt beskrivningen nedan.
Information om hur du använder minnesinternt för testning finns i det här avsnittet.
Håna eller stubba DbContext och DbSet
Den här metoden använder vanligtvis ett mock-ramverk för att skapa en testdubbel av DbContext och DbSet, och testar mot dessa testdubblar. Mockning DbContext kan vara en bra metod för att testa olika icke-frågefunktionalitet, till exempel anrop till Add eller SaveChanges(), så att du kan kontrollera att koden anropade dem i skrivscenarier.
Det går dock inte att DbSet simulera frågefunktioner korrekt, eftersom frågor uttrycks via LINQ-operatorer, som är anrop till statiska tilläggsmetoder över IQueryable. Som ett resultat, när vissa människor talar om att "håna DbSet", vad de egentligen menar är att de skapar en DbSet backad av en minnesintern samling och sedan utvärderar frågeoperatorer mot den samlingen i minnet, precis som en enkel IEnumerable. I stället för ett hån är detta faktiskt ett slags falskt, där den minnesinterna samlingen ersätter den verkliga databasen.
Eftersom endast DbSet i sig själv simuleras och frågan utvärderas i minnet, är denna metod mycket lik att använda EF Cores in-memory-provider: båda teknikerna kör frågeoperatorer i .NET över en minnesintern samling. Det innebär att den här tekniken också lider av samma nackdelar: frågor beter sig annorlunda (t.ex. runt skiftlägeskänslighet) eller misslyckas helt enkelt (t.ex. på grund av providerspecifika metoder), rå SQL fungerar inte och transaktioner ignoreras i bästa fall. Därför bör den här tekniken i allmänhet undvikas för testning av frågekod.
Databasmönster
Metoderna ovan försökte antingen byta ut EF Cores leverantör för produktionsdatabasen mot en falsk testleverantör eller att skapa en DbSet som backas upp av en minnesintern samling. Dessa tekniker liknar dem eftersom de fortfarande utvärderar programmets LINQ-frågor – antingen i SQLite eller i minnet – och detta är i slutändan källan till de svårigheter som beskrivs ovan: en fråga som är utformad för att köras mot en specifik produktionsdatabas kan inte köras på ett tillförlitligt sätt någon annanstans utan problem.
Om du vill ha ett korrekt, tillförlitligt testdubblett bör du överväga att införa ett lagringslager som förmedlar mellan programkoden och EF Core. Produktionsimplementeringen av lagringsplatsen innehåller de faktiska LINQ-frågorna och kör dem via EF Core. Under tester är lagringsplatsabstraktionen direkt stubbad eller mockad utan faktiska LINQ-frågor, vilket effektivt tar bort EF Core helt och hållet från din teststack och gör det möjligt för tester att fokusera enbart på programkod.
I följande diagram jämförs metoden för databasförfalskning (SQLite/minnesintern) med lagringsplatsens mönster:
Eftersom LINQ-frågor inte längre ingår i testningen kan du ange frågeresultat direkt till ditt program. Med andra metoder tillåter de tidigare metoderna i stort sett utstubbning av frågeindata (t.ex. att ersätta SQL Server-tabeller med minnesinterna), men kör sedan fortfarande de faktiska frågeoperatorerna i minnet. Med lagringsplatsens mönster kan du däremot skicka ut frågeutdata direkt, vilket möjliggör mycket mer kraftfulla och fokuserade enhetstester. Observera att för att detta ska fungera kan din lagringsplats inte exponera några IQueryable-returning-metoder, eftersom dessa återigen inte kan stubbas ut; IEnumerable ska returneras i stället.
Men eftersom lagringsplatsens mönster kräver inkapsling av varje (testbar) LINQ-fråga i en IEnumerable-returning-metod, medför det ytterligare ett arkitekturlager i ditt program och kan medföra betydande kostnader för att implementera och underhålla. Den här kostnaden bör inte rabatteras när du gör ett val om hur du testar ett program, särskilt med tanke på att tester mot den verkliga databasen fortfarande sannolikt kommer att behövas för de frågor som exponeras av lagringsplatsen.
Det är värt att notera att lagringsplatser har fördelar förutom att bara testa. De säkerställer att all dataåtkomstkod är koncentrerad på ett ställe i stället för att spridas runt programmet, och om ditt program behöver stöd för mer än en databas kan lagringsplatsens abstraktion vara till stor hjälp för att justera frågor mellan leverantörer.
Ett exempel som visar testning med en lagringsplats finns i det här avsnittet.
Övergripande jämförelse
Följande tabell innehåller en snabb, jämförande vy över de olika testteknikerna och visar vilka funktioner som kan testas enligt vilken metod:
| Feature | I-minnet | SQLite i minnet | Mock DbContext | Databasmönster | Genomföra tester mot databasen |
|---|---|---|---|---|---|
| Testa dubbel typ | Falskt | Falsk | Falsk | Mock/stub | Verklig, ingen dubbel |
| Raw SQL? | Nej | Det beror på | Nej | Yes | Yes |
| Transaktioner? | Nej (ignoreras) | Yes | Yes | Yes | Yes |
| Leverantörsspecifika översättningar? | Nej | Nej | Nej | Yes | Yes |
| Exakt frågebeteende? | Det beror på | Det beror på | Det beror på | Yes | Yes |
| Kan du använda LINQ var som helst i programmet? | Yes | Yes | Yes | Nej* | Yes |
* Alla testbara LINQ-frågor för databaser måste kapslas in i metoder i lager som returnerar IEnumerable för att bli stubbade/mockade.
Sammanfattning
- Vi rekommenderar att utvecklare har bra testtäckning av sina program som körs mot deras faktiska produktionsdatabassystem. Detta ger förtroende för att programmet faktiskt fungerar i produktion, och med rätt design kan tester köras på ett tillförlitligt och snabbt sätt. Eftersom dessa tester krävs i alla fall är det en bra idé att börja där, och om det behövs lägger du till tester med testdubblar senare, efter behov.
- Om du har valt att använda en testdubblett rekommenderar vi att du implementerar repositorie-mönstret, vilket möjliggör att du kan stubba eller mocka dataåtkomstlagret ovanför EF Core istället för att använda en falsk EF Core-provider (Sqlite/minnesintern) eller genom att
DbSetmocka. - Om lagringsplatsens mönster av någon anledning inte är ett genomförbart alternativ bör du överväga att använda minnesinterna SQLite-databaser.
- Undvik den minnesinterna providern i testsyfte – detta rekommenderas inte och stöds endast för äldre program.
- Undvik att
DbSethåna i frågesyfte.