Jak rozumiem, celem testów jednostkowych jest testowanie jednostek kodu osobno . To znaczy że:
- Nie powinny łamać się przez jakąkolwiek niepowiązaną zmianę kodu w innym miejscu w bazie kodu.
- Tylko jeden test jednostkowy powinien przełamać błąd w testowanej jednostce, w przeciwieństwie do testów integracyjnych (które mogą pęknąć w stosach).
Wszystko to implikuje, że każda zewnętrzna zależność testowanej jednostki powinna być wyśmiewana. Mam na myśli wszystkie zewnętrzne zależności , nie tylko „zewnętrzne warstwy”, takie jak sieć, system plików, baza danych itp.
Prowadzi to do logicznego wniosku, że praktycznie każdy test jednostkowy musi kpić . Z drugiej strony, szybkie wyszukiwanie w Google na temat kpienia ujawnia mnóstwo artykułów, które twierdzą, że „kpina jest zapachem kodu” i powinno się go w większości (choć nie całkowicie) unikać.
Teraz do pytania (pytań).
- Jak należy poprawnie pisać testy jednostkowe?
- Gdzie dokładnie leży granica między nimi a testami integracji?
Aktualizacja 1
Proszę wziąć pod uwagę następujący pseudo kod:
class Person {
constructor(calculator) {}
calculate(a, b) {
const sum = this.calculator.add(a, b);
// do some other stuff with the `sum`
}
}
Czy test, który testuje Person.calculate
metodę bez kpiny z Calculator
zależności (biorąc pod uwagę, że Calculator
jest to lekka klasa, która nie ma dostępu do „świata zewnętrznego”) można uznać za test jednostkowy?
źródło
Odpowiedzi:
Martin Fowler o teście jednostkowym
To, co napisał Kent Beck w Test Driven Development, na przykład
Każde twierdzenie o „punkcie testów jednostkowych” będzie w dużym stopniu zależeć od tego, jaką definicję „testu jednostkowego” rozważa się.
Jeśli twoja perspektywa jest taka, że twój program składa się z wielu małych jednostek, które są od siebie zależne, i jeśli ograniczasz się do stylu, który testuje każdą jednostkę w izolacji, to wiele podwójnych testów jest nieuniknionym wnioskiem.
Sprzeczna rada, którą widzisz, pochodzi od osób działających według innego zestawu założeń.
Na przykład, jeśli piszesz testy w celu wsparcia programistów podczas procesu refaktoryzacji, a podzielenie jednej jednostki na dwie jest refaktoryzacją, którą należy wesprzeć, wtedy coś musi dać. Może ten rodzaj testu wymaga innej nazwy? A może potrzebujemy innego zrozumienia „jednostki”.
Możesz porównać:
Myślę, że to niewłaściwe pytanie; to znowu spór o etykiety , kiedy uważam, że tak naprawdę zależy nam na właściwościach .
Wprowadzając zmiany w kodzie, nie dbam o izolację testów - już wiem, że „błąd” jest gdzieś w moim bieżącym stosie niezweryfikowanych edycji. Jeśli często uruchamiam testy, ograniczam głębokość tego stosu, a znalezienie błędu jest trywialne (w skrajnym przypadku testy są uruchamiane po każdej edycji - maksymalna głębokość stosu to jedna). Ale przeprowadzenie testów nie jest celem - jest przerwą - dlatego warto ograniczyć wpływ przerwania. Jednym ze sposobów ograniczenia przerw jest zapewnienie, że testy są szybkie ( Gary Bernhardt sugeruje 300 ms , ale nie wymyśliłem, jak to zrobić w moich okolicznościach).
Jeśli wywołanie
Calculator::add
nie wydłuży znacząco czasu potrzebnego do uruchomienia testu (lub jakiejkolwiek innej ważnej właściwości dla tego przypadku użycia), nie zawracam sobie głowy użyciem podwójnego testu - nie zapewnia korzyści przewyższających koszty .Zwróć uwagę na dwa założenia tutaj: istota ludzka w ramach oceny kosztów oraz krótki stos niezweryfikowanych zmian w ocenie korzyści. W okolicznościach, w których te warunki się nie utrzymują, wartość „izolacji” dość się zmienia.
Zobacz także Hot Lava , autor: Harry Percival.
źródło
Minimalizując skutki uboczne w kodzie.
Biorąc przykładowy kod, jeśli
calculator
na przykład rozmawia on z internetowym interfejsem API, albo tworzysz delikatne testy, które polegają na możliwości interakcji z tym internetowym interfejsem API, albo tworzysz jego próbę. Jeśli jednak jest to deterministyczny, bezstanowy zestaw funkcji obliczeniowych, to nie (i nie powinieneś) kpić z niego. Jeśli to zrobisz, ryzykujesz, że Twój makieta zachowuje się inaczej niż prawdziwy kod, co prowadzi do błędów w testach.Próbki powinny być potrzebne tylko w przypadku kodu, który odczytuje / zapisuje do systemu plików, baz danych, punktów końcowych adresów URL itp .; które są zależne od środowiska, w którym pracujesz; lub które mają charakter wysoce stanowy i niedeterministyczny. Jeśli więc ograniczysz te części kodu do minimum i ukryjesz je za abstrakcjami, łatwo będzie z nich wyśmiewać, a reszta kodu pozwoli uniknąć prób.
W przypadku punktów kodowych, które mają skutki uboczne, warto pisać testy, które kpią i testy, które nie. Te ostatnie wymagają jednak opieki, ponieważ z natury będą kruche i prawdopodobnie powolne. Możesz więc chcieć uruchamiać je tylko na noc na serwerze CI, a nie za każdym razem, gdy zapisujesz i budujesz swój kod. Poprzednie testy powinny być jednak przeprowadzane tak często, jak to możliwe. To, czy każdy test jest wtedy testem jednostkowym, czy testem integracyjnym, staje się akademickim i pozwala uniknąć „wojen płomieniowych” o to, co jest i nie jest testem jednostkowym.
źródło
R.equals
.). Ponieważ są to w większości funkcje czysto, na ogół nie są wyśmiewane w testach.Te pytania różnią się pod względem trudności. Najpierw weźmy pytanie 2.
Testy jednostkowe i testy integracyjne są wyraźnie oddzielone. Test jednostkowy testuje jedną jednostkę (metodę lub klasę) i wykorzystuje inne jednostki tylko w takim stopniu, w jakim jest to konieczne do osiągnięcia tego celu. Drwiny mogą być konieczne, ale nie o to chodzi w teście. Test integracji sprawdza interakcję między różnymi rzeczywistymi jednostkami. Ta różnica jest całkowitym powodem, dla którego potrzebujemy zarówno testów jednostkowych, jak i integracyjnych - gdyby jedno dobrze wykonało drugie, nie zrobilibyśmy tego, ale okazało się, że zwykle bardziej wydajne jest użycie dwóch specjalistycznych narzędzi niż jednego ogólnego narzędzia .
Teraz ważne pytanie: jak należy przeprowadzić test jednostkowy? Jak wspomniano powyżej, testy jednostkowe powinny konstruować struktury pomocnicze tylko w miarę potrzeby . Często łatwiej jest używać próbnej bazy danych niż prawdziwej bazy danych, a nawet jakiejkolwiek prawdziwej bazy danych. Wyśmiewanie samo w sobie nie ma jednak żadnej wartości. Jeśli często się zdarza, że w rzeczywistości łatwiej jest użyć rzeczywistych składników innej warstwy jako danych wejściowych do testu jednostkowego na średnim poziomie. Jeśli tak, nie wahaj się ich użyć.
Wielu praktyków obawia się, że jeśli test jednostkowy B ponownie wykorzysta klasy, które zostały już przetestowane w teście jednostkowym A, wówczas wada w jednostce A powoduje niepowodzenia testu w wielu miejscach. Uważam, że to nie problem: zestaw testowy ma odnieść sukces w 100% , aby dać Ci pewność co potrzeba, więc nie jest to duży problem, aby mieć zbyt wielu porażek - po tym wszystkim, zrobić mieć wadę. Jedynym krytycznym problemem byłby błąd, który spowodowałby zbyt małą liczbę awarii.
Dlatego nie twórzcie religii kpiny. To środek, a nie cel, więc jeśli uda ci się uniknąć dodatkowego wysiłku, powinieneś to zrobić.
źródło
The only critical problem would be if a defect triggered too few failures.
jest to jeden ze słabych punktów kpin. Musimy „zaprogramować” oczekiwane zachowanie, więc możemy się nie udać, powodując zakończenie testów jako „fałszywie pozytywne”. Ale kpina jest bardzo przydatną techniką w celu osiągnięcia determinizmu (najważniejszego warunku testowania). Używam ich we wszystkich moich projektach, jeśli to możliwe. Pokazują też, kiedy integracja jest zbyt złożona lub zależność zbyt ścisła.OK, aby bezpośrednio odpowiedzieć na pytania:
Jak mówisz, powinieneś kpić z zależności i testować tylko daną jednostkę.
Test integracji to test jednostkowy, w którym zależności nie są wyśmiewane.
Nie. Musisz wprowadzić zależność kalkulatora do tego kodu i masz wybór między wersją próbną a rzeczywistą. Jeśli użyjesz fałszywego, jest to test jednostkowy, jeśli użyjesz prawdziwego, to test integracyjny.
Jednak zastrzeżenie. czy naprawdę obchodzi Cię, jak ludzie myślą, że twoje testy powinny się nazywać?
Ale twoje prawdziwe pytanie wydaje się następujące:
Myślę, że problem polega na tym, że wiele osób używa próbnych elementów do pełnego odtworzenia zależności. Na przykład mogę wyśmiewać kalkulator w twoim przykładzie jako
Nie zrobiłbym czegoś takiego:
Twierdziłbym, że byłoby to „testowanie mojej makiety” lub „testowanie implementacji”. Powiedziałbym: „ Nie pisz Mocków! * W ten sposób”.
Inni ludzie by się ze mną nie zgodzili, zaczęlibyśmy na naszych blogach masowe wojny płomieni o najlepszy sposób na udawanie, co naprawdę nie miałoby sensu, chyba że zrozumiałbyś całe tło różnych podejść i naprawdę nie oferowałbyś zbyt dużej wartości do kogoś, kto chce po prostu pisać dobre testy.
źródło
MockCalc
naStubCalc
i nazwałbym to skrótem, a nie próbą. martinfowler.com/articles/…Moja ogólna zasada polega na tym, że właściwe testy jednostkowe:
Jeśli istnieją klasy narzędzi z frameworków, które komplikują testowanie jednostkowe, może nawet okazać się przydatne tworzenie bardzo prostych interfejsów i klas „otokowych” w celu ułatwienia kpienia z tych zależności. Owijarki te niekoniecznie podlegałyby testom jednostkowym.
Uważam, że to rozróżnienie jest najbardziej przydatne:
Jest tu szara strefa. Na przykład, jeśli możesz uruchomić aplikację w kontenerze Docker i uruchomić testy integracyjne jako ostatni etap kompilacji, a następnie zniszczyć kontener, to czy można uwzględnić te testy jako „testy jednostkowe”? Jeśli to jest twoja płonąca debata, jesteś w całkiem dobrym miejscu.
Nie. Niektóre indywidualne przypadki testowe dotyczą warunków błędu, takich jak przekazanie
null
jako parametr i sprawdzenie, czy otrzymano wyjątek. Wiele takich testów nie będzie wymagać żadnych prób. Ponadto implementacje, które nie mają skutków ubocznych, na przykład przetwarzanie ciągów lub funkcje matematyczne, mogą nie wymagać żadnych prób, ponieważ po prostu weryfikują dane wyjściowe. Ale myślę, że większość klas, które warto mieć, będzie wymagać co najmniej jednej makiety w kodzie testowym. (Im mniej, tym lepiej.)Wspomniany problem „zapachu kodu” powstaje, gdy masz zbyt skomplikowaną klasę, która wymaga długiej listy próbnych zależności w celu napisania testów. Jest to wskazówka, że musisz zmienić implementację i podzielić rzeczy, aby każda klasa miała mniejszy ślad i wyraźniejszą odpowiedzialność, a zatem łatwiej ją przetestować. Poprawi to jakość na dłuższą metę.
Nie sądzę, że jest to uzasadnione oczekiwanie, ponieważ działa przeciwko ponownemu użyciu. Możesz mieć
private
na przykład metodę, która jest wywoływana przez wielepublic
metod opublikowanych przez twój interfejs. Błąd wprowadzony do tej jednej metody może spowodować wiele niepowodzeń testu. Nie oznacza to, że powinieneś skopiować ten sam kod do każdejpublic
metody.źródło
Nie jestem do końca pewien, w jaki sposób ta zasada jest przydatna. Jeśli zmiana w jednej klasie / metodzie / czymkolwiek może zepsuć zachowanie innej w kodzie produkcyjnym, wówczas rzeczy są w rzeczywistości współpracownikami i nie są ze sobą niezwiązane. Jeśli testy się zepsują, a kod produkcyjny się nie powiedzie, to są podejrzane.
Uważam tę zasadę również za podejrzaną. Jeśli jesteś naprawdę wystarczająco dobry, aby ustrukturyzować swój kod i napisać testy tak, że jeden błąd powoduje dokładnie jeden błąd testu jednostkowego, to znaczy, że zidentyfikowałeś już wszystkie potencjalne błędy, nawet gdy baza kodów ewoluuje, aby używać przypadków, w których nie przewidziałem.
Nie sądzę, żeby to było ważne rozróżnienie. Czym w ogóle jest „jednostka” kodu?
Spróbuj znaleźć punkty wejścia, w których możesz pisać testy, które „mają sens” pod względem problematycznej domeny / reguł biznesowych, z którymi ma do czynienia ten poziom kodu. Często testy te mają charakter „funkcjonalny” - są wprowadzane do wejścia i sprawdzają, czy dane wyjściowe są zgodne z oczekiwaniami. Jeśli testy wyrażają pożądane zachowanie systemu, wówczas często pozostają dość stabilne, nawet jeśli kod produkcyjny ewoluuje i jest refaktoryzowany.
Nie odczytuj za dużo słowa „jednostka” i skłaniaj się do używania prawdziwych klas produkcyjnych w testach, nie martwiąc się zbytnio, jeśli angażujesz więcej niż jedną z nich w test. Jeśli jeden z nich jest trudny w użyciu (ponieważ wymaga dużo inicjalizacji lub musi trafić do prawdziwej bazy danych / serwera e-mail itp.), Pozwól swoim myślom przejść do drwiny / fałszowania.
źródło
Person:tellStory()
metodę, która łączy dane osoby w ciąg znaków, zwraca to, a następnie „historia” to prawdopodobnie jedna jednostka. Jeśli stworzę metodę prywatnego pomocnika, która ukrywa część kodu, nie sądzę, że wprowadziłem nową jednostkę - nie muszę tego testować osobno.Po pierwsze, niektóre definicje:
Test jednostkowy testuje jednostki w oderwaniu od innych jednostek, ale to, co to oznacza, nie jest konkretnie zdefiniowane przez żadne wiarygodne źródło, więc zdefiniujmy to nieco lepiej: jeśli granice We / Wy zostaną przekroczone (czy to We / Wy to sieć, dysk, ekran lub wejście interfejsu użytkownika), jest pół-obiektywne miejsce, w którym możemy narysować linię. Jeśli kod zależy od operacji we / wy, przekracza granicę jednostki i dlatego będzie musiał kpić z jednostki odpowiedzialnej za to operacje we / wy.
Zgodnie z tą definicją nie widzę przekonującego powodu, aby wyśmiewać takie rzeczy jak czyste funkcje, co oznacza, że testowanie jednostkowe nadaje się do czystych funkcji lub funkcji bez skutków ubocznych.
Jeśli chcesz testować jednostki z efektami, jednostki odpowiedzialne za efekty powinny być wyśmiewane, ale być może powinieneś rozważyć test integracji. Tak więc krótka odpowiedź brzmi: „jeśli chcesz kpić, zadaj sobie pytanie, czy tak naprawdę potrzebujesz testu integracji”. Ale jest lepsza, dłuższa odpowiedź tutaj, a królicza nora idzie znacznie głębiej. Makiety mogą być moim ulubionym zapachem kodu, ponieważ można się z nich wiele nauczyć.
Zapach kodu
W tym celu przejdziemy do Wikipedii:
Kontynuuje później ...
Innymi słowy, nie wszystkie zapachy kodowe są złe. Zamiast tego są powszechnymi wskazaniami, że coś może nie być wyrażone w optymalnej formie, a zapach może wskazywać na możliwość poprawienia danego kodu.
W przypadku kpiny zapach wskazuje, że jednostki, które wydają się wołać o kpiny, zależą od jednostek, które mają być kpione. Może to wskazywać, że nie rozłożyliśmy problemu na części, które można rozwiązać za pomocą atomów, a to może wskazywać na wadę projektową oprogramowania.
Istotą całego rozwoju oprogramowania jest proces dzielenia dużego problemu na mniejsze, niezależne części (rozkład) i komponowanie rozwiązań w celu utworzenia aplikacji, która rozwiązuje duży problem (kompozycję).
Wyśmiewanie jest wymagane, gdy jednostki użyte do rozbicia dużego problemu na mniejsze części zależą od siebie. Innymi słowy, kpina jest wymagana, gdy nasze domniemane atomowe jednostki składu nie są tak naprawdę atomowe, a nasza strategia rozkładu nie zdołała rozłożyć większego problemu na mniejsze, niezależne problemy do rozwiązania.
To, co powoduje, że kpiny pachną kodem, nie polega na tym, że z mockiem nie ma nic złego - czasem jest to bardzo przydatne. To, co powoduje, że kod ma zapach, może wskazywać na problematyczne źródło sprzężenia w Twojej aplikacji. Czasami usunięcie tego źródła sprzężenia jest o wiele bardziej produktywne niż pisanie próbnego.
Istnieje wiele rodzajów sprzężenia, a niektóre są lepsze od innych. Zrozumienie, że makiety są zapachem kodu, może nauczyć cię rozpoznawania i unikania najgorszych rodzajów na wczesnym etapie cyklu życia aplikacji, zanim zapach zmieni się w coś gorszego.
źródło
Wyśmiewanie powinno być stosowane tylko w ostateczności, nawet w testach jednostkowych.
Metoda nie jest jednostką, a nawet klasa nie jest jednostką. Jednostka to logiczne oddzielenie kodu, które ma sens, niezależnie od tego, jak go nazwiesz. Ważnym elementem dobrze przetestowanego kodu jest możliwość swobodnego refaktoryzacji, a część możliwości swobodnego refaktoryzacji oznacza, że nie musisz zmieniać swoich testów, aby to zrobić. Im więcej kpisz, tym bardziej musisz zmieniać testy podczas refaktoryzacji. Jeśli uważasz, że metoda jest jednostką, musisz zmieniać testy za każdym razem, gdy dokonujesz refaktoryzacji. A jeśli uważasz klasę za jednostkę, musisz zmieniać testy za każdym razem, gdy chcesz podzielić klasę na kilka klas. Kiedy trzeba przefakturować swoje testy, aby przefaktoryzować kod, ludzie decydują się nie refaktoryzować swojego kodu, co jest najgorszą rzeczą, jaka może się przydarzyć projektowi. Bardzo ważne jest, abyś mógł podzielić klasę na wiele klas bez konieczności ponownego testowania swoich testów, w przeciwnym razie skończysz na ponad 500 liniowych klasach spaghetti. Jeśli traktujesz metody lub klasy jako swoje jednostki z testowaniem jednostkowym, prawdopodobnie nie robisz programowania obiektowego, ale jakiegoś zmutowanego programowania funkcjonalnego z obiektami.
Wyodrębnienie kodu do testu jednostkowego nie oznacza, że wyśmiewasz wszystko poza nim. Gdyby tak było, musiałbyś kpić z lekcji matematyki w swoim języku i absolutnie nikt nie uważa, że to dobry pomysł. Zależności wewnętrzne nie powinny być traktowane inaczej niż zależności zewnętrzne. Ufasz, że są dobrze przetestowane i działają tak, jak powinny. Jedyną prawdziwą różnicą jest to, że jeśli wewnętrzne zależności psują moduły, możesz przerwać to, co robisz, aby to naprawić, zamiast publikować problem na GitHub i albo zagłębić się w bazę kodu, której nie rozumiesz, aby to naprawić. lub mam nadzieję na najlepsze.
Izolowanie kodu oznacza po prostu, że traktujesz swoje wewnętrzne zależności jak czarne skrzynki i nie testujesz rzeczy, które się w nich zachodzą. Jeśli masz moduł B, który akceptuje dane wejściowe 1, 2 lub 3, i masz moduł A, który go wywołuje, nie masz testów dla modułu A dla każdej z tych opcji, po prostu wybierz jedną z nich i użyj jej. Oznacza to, że twoje testy dla modułu A powinny przetestować różne sposoby traktowania odpowiedzi z modułu B, a nie rzeczy, które do niego przechodzisz.
Tak więc, jeśli kontroler przekazuje złożony obiekt do zależności, a ta zależność robi kilka możliwych rzeczy, być może zapisując ją do bazy danych i może zwracając różne błędy, ale tak naprawdę wszystko, co robi, to po prostu sprawdzić, czy zwraca błąd lub nie i przekaż te informacje razem, wtedy wszystko, co testujesz w kontrolerze, to jeden test na to, czy zwraca błąd i przekazuje go wraz z drugim testem, jeśli nie zwraca błędu. Nie testujesz, czy coś zostało zapisane w bazie danych lub jakiego rodzaju błąd to błąd, ponieważ byłby to test integracyjny. Aby to zrobić, nie musisz kpić z zależności. Wyizolowałeś kod.
źródło