Wybieranie strategii testowania
Jak wspomniano w temacie Przegląd, podstawową decyzją, którą należy podjąć, jest to, czy testy będą obejmować system produkcyjnej bazy danych — podobnie jak w przypadku aplikacji — czy testy będą uruchamiane względem testu dwukrotnie, co zastępuje system produkcyjnej bazy danych.
Testowanie względem rzeczywistego zasobu zewnętrznego — zamiast zastępowania go testem podwójnym — może obejmować następujące trudności:
- W wielu przypadkach po prostu nie jest możliwe lub praktyczne przetestowanie rzeczywistego zasobu zewnętrznego. Na przykład aplikacja może wchodzić w interakcję z usługą, której nie można łatwo przetestować (ze względu na ograniczenie szybkości lub brak środowiska testowego).
- Nawet jeśli istnieje możliwość zaangażowania rzeczywistego zasobu zewnętrznego, może to być znacznie powolne: uruchomienie dużej ilości testów w usłudze w chmurze może spowodować, że testy będą trwać zbyt długo. Testowanie powinno być częścią codziennego przepływu pracy dewelopera, dlatego ważne jest, aby testy działały szybko.
- Wykonywanie testów względem zasobu zewnętrznego może obejmować problemy z izolacją, w których testy ingerują ze sobą. Na przykład wiele testów działających równolegle względem bazy danych może modyfikować dane i powodować niepowodzenie na różne sposoby. Użycie testu dwukrotnie pozwala uniknąć tego, ponieważ każdy test jest uruchamiany względem własnego zasobu w pamięci i dlatego jest naturalnie odizolowany od innych testów.
Jednak testy, które przechodzą względem testu dwukrotnie, nie gwarantują, że program działa podczas uruchamiania względem rzeczywistego zasobu zewnętrznego. Na przykład test bazy danych podwójnie może wykonywać porównania ciągów z uwzględnieniem wielkości liter, podczas gdy system produkcyjnej bazy danych wykonuje porównania bez uwzględniania wielkości liter. Takie problemy są wykrywane tylko wtedy, gdy testy są wykonywane względem rzeczywistej produkcyjnej bazy danych, co czyni te testy ważną częścią każdej strategii testowania.
Testowanie bazy danych może być łatwiejsze niż się wydaje
Ze względu na powyższe trudności z testowaniem w rzeczywistej bazie danych deweloperzy często są zachęcani do korzystania z dubletów testowych i mają niezawodny zestaw testów, który mogą uruchamiać często na swoich maszynach; testy z udziałem bazy danych, natomiast mają być wykonywane znacznie rzadziej, a w wielu przypadkach również zapewniają znacznie mniej pokrycia. Zalecamy bardziej przemyślanie tej drugiej i sugerujemy, że bazy danych mogą być znacznie mniej dotknięte powyższymi problemami niż ludzie wydają się myśleć:
- Większość baz danych można w dzisiejszych czasach łatwo zainstalować na maszynie dewelopera. Technologie oparte na kontenerach, takie jak Platforma Docker, mogą sprawić, że będzie to bardzo łatwe, a technologie, takie jak Obszary robocze github i kontener deweloperski , skonfigurują całe środowisko programistyczne (w tym bazę danych). W przypadku korzystania z SQL Server można również przetestować bazę danych LocalDB w systemie Windows lub łatwo skonfigurować obraz platformy Docker w systemie Linux.
- Testowanie względem lokalnej bazy danych — z rozsądnym testowym zestawem danych — jest zwykle bardzo szybkie: komunikacja jest całkowicie lokalna, a dane testowe są zwykle buforowane w pamięci po stronie bazy danych. Sam program EF Core zawiera ponad 30 000 testów w odniesieniu do SQL Server sam. Są one niezawodne w ciągu kilku minut, wykonywane w ciągłej integracji na każdym zatwierdzeniu i są bardzo często wykonywane przez deweloperów lokalnie. Niektórzy deweloperzy zwracają się do bazy danych w pamięci ("fałszywe") w przekonaniu, że jest to potrzebne do szybkości - to prawie nigdy nie jest w rzeczywistości przypadkiem.
- Izolacja jest rzeczywiście przeszkodą podczas uruchamiania testów względem prawdziwej bazy danych, ponieważ testy mogą modyfikować dane i zakłócać wzajemnie. Istnieją jednak różne techniki zapewnienia izolacji w scenariuszach testowania bazy danych; Koncentrujemy się na tych testach w systemie produkcyjnej bazy danych).
Powyższe nie ma na celu dysparowania testowych podwojeń ani argumentowania przeciwko ich używaniu. Po pierwsze, testy podwójne są niezbędne w przypadku niektórych scenariuszy, których nie można przetestować w przeciwnym razie, takich jak symulowanie awarii bazy danych. Jednak w naszym doświadczeniu użytkownicy często niechętnie od testowania bazy danych z powyższych powodów, wierząc, że jest to powolne, trudne lub niewiarygodne, gdy tak niekoniecznie jest. Testowanie w systemie produkcyjnej bazy danych ma na celu rozwiązanie tego problemu, zapewniając wytyczne i przykłady do pisania szybkich, izolowanych testów w bazie danych.
Różne typy podwajań testów
Test doubles to szeroki termin, który obejmuje bardzo różne podejścia. W tej sekcji opisano kilka typowych technik obejmujących podwójne testy na potrzeby testowania aplikacji EF Core:
- Użyj narzędzia SQLite (w trybie w pamięci) jako fałszywej bazy danych, zastępując system produkcyjnej bazy danych.
- Użyj dostawcy ef Core w pamięci jako fałszywej bazy danych, zastępując system produkcyjnej bazy danych.
- Wyśmiewane lub wycięcie i
DbContextDbSet. - Wprowadzenie warstwy repozytorium między programem EF Core i kodem aplikacji oraz pozorowanie lub wycinkę tej warstwy.
Poniżej dowiemy się, co oznacza każda metoda i porównamy ją z innymi. Zalecamy zapoznanie się z różnymi metodami w celu uzyskania pełnego zrozumienia każdego z nich. Jeśli zdecydujesz się napisać testy, które nie obejmują produkcyjnego systemu bazy danych, niż warstwa repozytorium jest jedynym podejściem umożliwiającym kompleksowe i niezawodne wyśmiewanie/szydzenie warstwy danych. Jednak takie podejście ma znaczny koszt w zakresie implementacji i konserwacji.
SQLite jako fałszywe bazy danych
Jedną z możliwych metod testowania jest zamiana produkcyjnej bazy danych (np. SQL Server) na sqLite, efektywnie używając jej jako "fałszywej". Oprócz łatwości konfiguracji sqLite ma funkcję bazy danych w pamięci , która jest szczególnie przydatna do testowania: każdy test jest naturalnie izolowany we własnej bazie danych w pamięci i nie trzeba zarządzać rzeczywistymi plikami.
Jednak przed wykonaniem tej czynności ważne jest, aby zrozumieć, że w programie EF Core różni dostawcy baz danych zachowują się inaczej — program EF Core nie podejmuje próby abstrakcjonowania każdego aspektu bazowego systemu baz danych. Zasadniczo oznacza to, że testowanie względem sqLite nie gwarantuje takich samych wyników, jak w przypadku SQL Server ani żadnej innej bazy danych. Oto kilka przykładów możliwych różnic behawioralnych:
- To samo zapytanie LINQ może zwracać różne wyniki dla różnych dostawców. Na przykład SQL Server domyślnie porównuje ciągi bez uwzględniania wielkości liter, natomiast funkcja SQLite jest rozróżniana wielkość liter. Może to sprawić, że testy przejdą do bazy danych SQLite, w której nie będą działać w SQL Server (lub odwrotnie).
- Niektóre zapytania, które działają na SQL Server po prostu nie są obsługiwane w sqLite, ponieważ dokładna obsługa SQL w tych dwóch bazach danych różni się.
- Jeśli zapytanie ma miejsce przy użyciu metody specyficznej dla dostawcy, takiej jak SQL Server
EF.Functions.DateDiffDay, to zapytanie zakończy się niepowodzeniem w języku SQLite i nie będzie można go przetestować. - Raw SQL może działać lub może zakończyć się niepowodzeniem lub zwrócić różne wyniki, w zależności od dokładnie tego, co jest wykonywane. Dialekty SQL różnią się na wiele sposobów w różnych bazach danych.
W porównaniu do uruchamiania testów w systemie produkcyjnej bazy danych stosunkowo łatwo rozpocząć pracę z sqLite, a tak wielu użytkowników. Niestety powyższe ograniczenia zwykle stają się problematyczne podczas testowania aplikacji EF Core, nawet jeśli nie wydają się znajdować na początku. W związku z tym zalecamy napisanie testów względem rzeczywistej bazy danych lub użycie testu podwójnej jest absolutną koniecznością, biorąc pod uwagę koszt wzorca repozytorium, jak opisano poniżej.
Aby uzyskać informacje na temat używania narzędzia SQLite do testowania, zobacz tę sekcję.
W pamięci jako fałszywe bazy danych
Alternatywą dla sqLite jest również platforma EF Core z dostawcą w pamięci. Chociaż ten dostawca został pierwotnie zaprojektowany do obsługi wewnętrznych testów platformy EF Core, niektórzy deweloperzy używają go jako fałszywej bazy danych podczas testowania aplikacji EF Core. Jest to bardzo zniechęcane: jako fałszywe bazy danych w pamięci występują takie same problemy jak SQLite (patrz powyżej), ale ponadto mają następujące dodatkowe ograniczenia:
- Dostawca w pamięci zazwyczaj obsługuje mniej typów zapytań niż dostawca SQLite, ponieważ nie jest to relacyjna baza danych. Więcej zapytań zakończy się niepowodzeniem lub będzie działać inaczej w porównaniu z produkcyjną bazą danych.
- Transakcje nie są obsługiwane.
- Nieprzetworzony program SQL jest całkowicie nieobsługiwany. Porównaj to z sqLite, gdzie można używać nieprzetworzonego języka SQL, o ile usługa SQL działa w taki sam sposób w środowisku SQLite i produkcyjnej bazie danych.
- Dostawca w pamięci nie został zoptymalizowany pod kątem wydajności i zazwyczaj działa wolniej niż SQLite w trybie w pamięci (a nawet w systemie produkcyjnej bazy danych).
Podsumowując, w pamięci wszystkie wady SQLite, wraz z kilkoma kolejnymi - i nie oferują żadnych korzyści w zamian. Jeśli szukasz prostej, fałszywej bazy danych w pamięci, użyj narzędzia SQLite zamiast dostawcy w pamięci; należy jednak rozważyć użycie wzorca repozytorium, jak opisano poniżej.
Aby uzyskać informacje na temat używania w pamięci do testowania, zobacz tę sekcję.
Wyśmiewanie lub stubbing DbContext i DbSet
Takie podejście zwykle używa makiety struktury do utworzenia testowego podwojenia DbContext wartości i i DbSettestów względem tych podwojeń. Pozorowanie DbContext może być dobrym rozwiązaniem do testowania różnych funkcji niezwiązanych z zapytaniami , takich jak wywołania Add lub SaveChanges(), co pozwala sprawdzić, czy kod został nazwany w scenariuszach zapisu.
Jednak prawidłowe wyśmiewanie DbSet funkcji zapytania nie jest możliwe, ponieważ zapytania są wyrażane za pośrednictwem operatorów LINQ, które są wywołaniami statycznych metod rozszerzenia za pośrednictwem IQueryablemetody . W rezultacie, gdy niektórzy ludzie mówią o "kpieniu DbSet", co naprawdę oznaczają, jest to, że tworzą DbSet wspierane przez kolekcję w pamięci, a następnie oceniają operatory zapytań względem tej kolekcji w pamięci, podobnie jak proste IEnumerable. Zamiast pozorować, jest to w rzeczywistości rodzaj fałszywej kolekcji, w której kolekcja w pamięci zastępuje rzeczywistą bazę danych.
Ponieważ tylko DbSet samo jest fałszywe, a zapytanie jest oceniane w pamięci, to podejście kończy się bardzo podobne do korzystania z dostawcy EF Core w pamięci: obie techniki wykonują operatory zapytań na platformie .NET za pośrednictwem kolekcji w pamięci. W rezultacie ta technika cierpi również na te same wady: zapytania będą zachowywać się inaczej (np. wokół poufności liter) lub po prostu zakończy się niepowodzeniem (np. z powodu metod specyficznych dla dostawcy), nieprzetworzone dane SQL nie będą działać, a transakcje będą ignorowane w najlepszym razie. W związku z tym ta technika powinna być zwykle unikana do testowania kodu zapytania.
Wzorzec repozytorium
Powyższe podejścia próbowały zamienić dostawcę produkcyjnej bazy danych platformy EF Core na fałszywego dostawcę testowania lub utworzyć DbSet kopię zapasową kolekcji w pamięci. Te techniki są podobne, ponieważ nadal oceniają zapytania LINQ programu — w sqLite lub w pamięci — i jest to ostatecznie źródło trudności opisanych powyżej: zapytanie zaprojektowane do wykonania względem określonej produkcyjnej bazy danych nie może niezawodnie wykonywać w innym miejscu bez problemów.
Aby uzyskać odpowiedni, niezawodny test dwukrotnie, rozważ wprowadzenie warstwy repozytorium , która pośredniczy między kodem aplikacji a platformą EF Core. Implementacja produkcyjna repozytorium zawiera rzeczywiste zapytania LINQ i wykonuje je za pośrednictwem platformy EF Core. W przypadku testowania abstrakcja repozytorium jest bezpośrednio wyśmiewane lub wyśmiewane bez konieczności wykonywania rzeczywistych zapytań LINQ, skutecznie usuwając program EF Core ze stosu testowego i pozwalając testom skupić się tylko na kodzie aplikacji.
Poniższy diagram porównuje fałszywe podejście bazy danych (SQLite/in-memory) ze wzorcem repozytorium:

Ponieważ zapytania LINQ nie są już częścią testowania, możesz bezpośrednio dostarczyć wyniki zapytania do aplikacji. Mówiąc inaczej, poprzednie podejścia w przybliżeniu zezwalają na odcięcie danych wejściowych zapytań (np. zastępowanie tabel SQL Server przy użyciu tabel w pamięci), ale następnie wykonywanie rzeczywistych operatorów zapytań w pamięci. Wzorzec repozytorium, z kolei, umożliwia bezpośrednie wyróżnianie danych wyjściowych zapytań , co pozwala na znacznie bardziej zaawansowane i skoncentrowane testowanie jednostkowe. Należy pamiętać, że aby to działanie działało, repozytorium nie może uwidocznić żadnych metod zwracanych przez funkcję IQueryable, ponieważ nie można ich ponownie usunąć; Zamiast tego należy zwrócić element IEnumerable.
Jednak ponieważ wzorzec repozytorium wymaga hermetyzacji każdego zapytania LINQ z możliwością testowania w metodzie zwracanej przez IEnumerable, nakłada dodatkową warstwę architektury na aplikację i może ponieść znaczne koszty implementacji i konserwacji. Ten koszt nie powinien być dyskontowany podczas wyboru sposobu testowania aplikacji, szczególnie biorąc pod uwagę, że testy względem rzeczywistej bazy danych nadal mogą być potrzebne dla zapytań uwidocznionych przez repozytorium.
Warto zauważyć, że repozytoria mają zalety poza testowaniem. Zapewniają one skoncentrowanie całego kodu dostępu do danych w jednym miejscu, a nie rozpowszechnianie wokół aplikacji, a jeśli aplikacja musi obsługiwać więcej niż jedną bazę danych, abstrakcja repozytorium może być bardzo przydatna do dostosowywania zapytań między dostawcami.
Aby zapoznać się z przykładem przedstawiającym testowanie za pomocą repozytorium, zobacz tę sekcję.
Porównanie ogólne
Poniższa tabela zawiera szybki, porównawczy widok różnych technik testowania i pokazuje, które funkcje można przetestować w ramach tego podejścia:
| Cecha | W pamięci | SqLite w pamięci | Mock DbContext | Wzorzec repozytorium | Testowanie względem bazy danych |
|---|---|---|---|---|---|
| Test podwójny typ | Fałszywe | Fałszywe | Fałszywe | Mock/stub | Real, bez podwójnego |
| Nieprzetworzone bazy danych SQL? | Nie | Zależy | Nie | Tak | Tak |
| Transakcji? | Nie (ignorowane) | Tak | Tak | Tak | Tak |
| Tłumaczenia specyficzne dla dostawcy? | Nie | Nie | Nie | Tak | Tak |
| Dokładne zachowanie zapytań? | Zależy | Zależy | Zależy | Tak | Tak |
| Czy można używać LINQ w dowolnym miejscu w aplikacji? | Tak | Tak | Tak | Nie* | Tak |
* Wszystkie testowalne zapytania LINQ bazy danych muszą być hermetyzowane w metodach IEnumerable-returning repozytorium, aby można je było wyśmiewać/wyśmiewać.
Podsumowanie
- Zalecamy, aby deweloperzy mieli dobry zakres testowy aplikacji działających w rzeczywistym produkcyjnym systemie bazy danych. Zapewnia to pewność, że aplikacja rzeczywiście działa w środowisku produkcyjnym, a przy odpowiednim projektowaniu testy mogą być wykonywane niezawodnie i szybko. Ponieważ te testy są wymagane w każdym przypadku, dobrym pomysłem jest rozpoczęcie tam, a w razie potrzeby dodanie testów przy użyciu podwaja się później, zgodnie z potrzebami.
- Jeśli zdecydujesz się użyć podwójnego testu, zalecamy zaimplementowanie wzorca repozytorium, co pozwala na wycinkę lub wyśmiewanie warstwy dostępu do danych powyżej platformy EF Core, zamiast używania fałszywego dostawcy platformy EF Core (Sqlite/in-memory) lub przez pozorowanie
DbSet. - Jeśli z jakiegoś powodu wzorzec repozytorium nie jest realną opcją, rozważ użycie baz danych w pamięci SQLite.
- Unikaj dostawcy w pamięci do celów testowych — jest to niezalecone i obsługiwane tylko w przypadku starszych aplikacji.
- Unikaj pozorowania
DbSetw celach związanych z wykonywaniem zapytań.