Piszę komponent, który mając plik ZIP, musi:
- Rozpakuj plik.
- Znajdź konkretną bibliotekę dll wśród rozpakowanych plików.
- Załaduj tę bibliotekę dll przez odbicie i wywołaj na niej metodę.
Chciałbym przetestować jednostkowo ten komponent.
Kusi mnie, aby napisać kod, który zajmuje się bezpośrednio systemem plików:
void DoIt()
{
Zip.Unzip(theZipFile, "C:\\foo\\Unzipped");
System.IO.File myDll = File.Open("C:\\foo\\Unzipped\\SuperSecret.bar");
myDll.InvokeSomeSpecialMethod();
}
Ale ludzie często mówią: „Nie pisz testów jednostkowych, które opierają się na systemie plików, bazie danych, sieci itp.”
Gdybym napisał to w sposób przyjazny dla testów jednostkowych, przypuszczam, że wyglądałoby to tak:
void DoIt(IZipper zipper, IFileSystem fileSystem, IDllRunner runner)
{
string path = zipper.Unzip(theZipFile);
IFakeFile file = fileSystem.Open(path);
runner.Run(file);
}
Yay! Teraz jest to testowalne; Mogę karmić testowymi dublami (próbami) metodą DoIt. Ale jakim kosztem? Musiałem teraz zdefiniować 3 nowe interfejsy, aby było to testowalne. A co dokładnie testuję? Testuję, czy mój DoIt działa poprawnie z jego zależnościami. Nie sprawdza, czy plik zip został poprawnie rozpakowany itp.
Nie wydaje mi się, żebym już testował funkcjonalność. Mam wrażenie, że właśnie testuję interakcje w klasie.
Moje pytanie brzmi : jaki jest właściwy sposób testowania jednostkowego czegoś, co jest zależne od systemu plików?
edytuj Używam .NET, ale koncepcja może również zastosować Java lub kod natywny.
źródło
myDll.InvokeSomeSpecialMethod();
, byłoby sprawdzenie, czy działa poprawnie zarówno w sytuacjach sukcesu, jak i niepowodzenia, więc nie przeprowadzałbym testów jednostkowych,DoIt
aleDllRunner.Run
powiedziałbym, że niewłaściwe użycie testu UNIT do podwójnego sprawdzenia, czy cały proces działa, byłoby dopuszczalne niewłaściwe użycie i ponieważ byłby to test integracyjnyOdpowiedzi:
Naprawdę nie ma w tym nic złego, to tylko kwestia tego, czy nazwiesz to testem jednostkowym czy testem integracji. Musisz tylko upewnić się, że w przypadku interakcji z systemem plików nie wystąpią żadne niezamierzone skutki uboczne. W szczególności upewnij się, że posprzątałeś po sobie - usuń wszystkie utworzone pliki tymczasowe - i nie nadpisujesz przypadkowo istniejącego pliku, który miał taką samą nazwę jak plik tymczasowy, którego używasz. Zawsze używaj ścieżek względnych, a nie ścieżek bezwzględnych.
Dobrym pomysłem byłoby również
chdir()
przejście do katalogu tymczasowego przed uruchomieniem testu ichdir()
później.źródło
chdir()
obejmuje cały proces, więc możesz zepsuć możliwość równoległego uruchamiania testów, jeśli Twoja platforma testowa lub przyszła jej wersja to obsługują.Trafiłeś w gwóźdź prosto w jego głowę. To, co chcesz przetestować, to logika metody, niekoniecznie to, czy można zaadresować prawdziwy plik. Nie musisz testować (w tym teście jednostkowym), czy plik jest poprawnie rozpakowany, Twoja metoda przyjmuje to za pewnik. Interfejsy są cenne same w sobie, ponieważ zapewniają abstrakcje, względem których można programować, zamiast pośrednio lub jawnie polegać na jednej konkretnej implementacji.
źródło
DoIt
funkcja, jak podano, nie wymaga nawet testowania. Jak słusznie zauważył pytający, nie ma już nic istotnego do sprawdzenia. Teraz to realizacjaIZipper
,IFileSystem
iIDllRunner
że potrzeby testowania, ale one są właśnie rzeczy, które zostały wyśmiewali się do testu!Twoje pytanie odsłania jedną z najtrudniejszych części testowania dla programistów, którzy dopiero się w to wkręcają:
- Co do cholery mam testować?
Twój przykład nie jest zbyt interesujący, ponieważ po prostu skleja ze sobą niektóre wywołania API, więc gdybyś napisał dla niego test jednostkowy, zakończyłbyś po prostu stwierdzeniem, że metody zostały wywołane. Takie testy ściśle wiążą szczegóły implementacji z testem. To jest złe, ponieważ teraz musisz zmieniać test za każdym razem, gdy zmieniasz szczegóły implementacji metody, ponieważ zmiana szczegółów implementacji psuje test (y)!
Posiadanie złych testów jest w rzeczywistości gorsze niż brak testów.
W twoim przykładzie:
Chociaż możesz przekazać fałszywe informacje, nie ma logiki w metodzie do przetestowania. Gdybyś spróbował wykonać w tym celu test jednostkowy, może to wyglądać mniej więcej tak:
Gratulacje, po prostu skopiowałeś szczegóły implementacji swojej
DoIt()
metody do testu. Miłego utrzymania.Pisząc testy, chcesz przetestować CO, a nie JAK . Zobacz Testowanie czarnoskrzynkowe uzyskać więcej informacji, .
CO jest nazwa metody (a przynajmniej powinien być). JAK są małe szczegóły implementacji, które żyją wewnątrz metody. Dobre testy pozwalają zamienić JAK bez przerywania CO .
Pomyśl o tym w ten sposób, zadaj sobie pytanie:
„Jeśli zmienię szczegóły implementacji tej metody (bez zmiany zamówienia publicznego), czy spowoduje to przerwanie mojego testu (-ów)?”
Jeśli odpowiedź brzmi tak, testujesz JAK, a nie CO .
Aby odpowiedzieć na konkretne pytanie dotyczące testowania kodu z zależnościami systemu plików, powiedzmy, że miałeś coś bardziej interesującego z plikiem i chciałeś zapisać zawartość a zakodowaną w Base64
byte[]
do pliku. Możesz użyć do tego strumieni, aby sprawdzić, czy Twój kod działa prawidłowo, bez konieczności sprawdzania, jak to robi. Przykładem może być coś takiego (w Javie):Test wykorzystuje
ByteArrayOutputStream
jednak w aplikacji (za pomocą iniekcji zależność) prawdziwy StreamFactory (chyba nazywa FileStreamFactory) wróciFileOutputStream
zoutputStream()
i by napisać doFile
.To, co było interesujące w tej
write
metodzie, to fakt, że zapisywała zawartość zakodowaną w Base64, więc właśnie to testowaliśmy. W przypadku TwojejDoIt()
metody byłoby to lepiej przetestowane za pomocą testu integracji .źródło
Nie mam zamiaru zanieczyszczać mojego kodu typami i koncepcjami, które istnieją tylko w celu ułatwienia testowania jednostkowego. Jasne, jeśli dzięki temu projekt będzie czystszy i lepszy, to świetny, ale myślę, że często tak nie jest.
Uważam, że testy jednostkowe zrobiłyby tyle, ile mogą, co może nie być 100% pokryciem. W rzeczywistości może to być tylko 10%. Chodzi o to, że testy jednostkowe powinny być szybkie i nie mieć żadnych zewnętrznych zależności. Mogą testować przypadki, takie jak „ta metoda zgłasza ArgumentNullException po przekazaniu wartości null dla tego parametru”.
Następnie dodałbym testy integracyjne (również zautomatyzowane i prawdopodobnie przy użyciu tej samej struktury testów jednostkowych), które mogą mieć zewnętrzne zależności i testować scenariusze kompleksowe, takie jak te.
Podczas pomiaru pokrycia kodu mierzę zarówno testy jednostkowe, jak i integracyjne.
źródło
Nie ma nic złego w trafieniu do systemu plików, po prostu potraktuj to jako test integracji, a nie test jednostkowy. Zamieniłbym trwale zakodowaną ścieżkę na ścieżkę względną i utworzyłbym podfolder TestData, aby zawierał suwaki dla testów jednostkowych.
Jeśli testy integracji trwają zbyt długo, oddziel je, aby nie były uruchamiane tak często, jak szybkie testy jednostkowe.
Zgadzam się, czasami myślę, że testowanie oparte na interakcji może powodować zbyt wiele sprzężeń i często kończy się niewystarczającą wartością. Naprawdę chcesz tutaj przetestować rozpakowywanie pliku, a nie tylko sprawdzić, czy wywołujesz właściwe metody.
źródło
Jednym ze sposobów byłoby napisanie metody unzip, która pobierze InputStreams. Następnie test jednostkowy mógłby skonstruować taki InputStream z tablicy bajtów przy użyciu ByteArrayInputStream. Zawartość tej tablicy bajtów może być stałą w kodzie testu jednostkowego.
źródło
Wydaje się, że jest to bardziej test integracji, ponieważ polegasz na konkretnym szczególe (systemie plików), który teoretycznie może się zmienić.
Abstrahowałbym kod, który zajmuje się systemem operacyjnym, do jego własnego modułu (klasa, zespół, jar, cokolwiek). W twoim przypadku chcesz załadować konkretną bibliotekę DLL, jeśli zostanie znaleziona, więc utwórz interfejs IDllLoader i klasę DllLoader. Niech Twoja aplikacja pobierze bibliotekę DLL z DllLoader za pomocą interfejsu i przetestuj to… nie jesteś jednak odpowiedzialny za rozpakowywanie kodu, prawda?
źródło
Zakładając, że „interakcje systemu plików” są dobrze przetestowane w samym frameworku, utwórz metodę do pracy ze strumieniami i przetestuj ją. Otwarcie FileStream i przekazanie go do metody można pominąć w testach, ponieważ FileStream.Open jest dobrze przetestowany przez twórców frameworka.
źródło
Nie należy testować interakcji klas i wywołań funkcji. zamiast tego powinieneś rozważyć testy integracyjne. Przetestuj wymagany wynik, a nie operację ładowania pliku.
źródło
W przypadku testów jednostkowych sugerowałbym włączenie pliku testowego do projektu (plik EAR lub odpowiednik), a następnie użycie ścieżki względnej w testach jednostkowych, tj. „../Testdata/testfile”.
Tak długo, jak projekt jest poprawnie eksportowany / importowany, test jednostkowy powinien działać.
źródło
Jak powiedzieli inni, pierwszy jest w porządku jako test integracji. Druga sprawdza tylko to, co funkcja ma faktycznie robić, czyli wszystko, co powinien zrobić test jednostkowy.
Jak pokazano, drugi przykład wygląda trochę bez sensu, ale daje możliwość sprawdzenia, jak funkcja reaguje na błędy w którymkolwiek z kroków. W przykładzie nie ma żadnego sprawdzania błędów, ale w prawdziwym systemie możesz mieć, a wstrzyknięcie zależności pozwoli ci przetestować wszystkie odpowiedzi na wszelkie błędy. Wtedy koszt będzie tego wart.
źródło