W komentarzu do tego wspaniałego postu Roy Osherove wspomniał o projekcie OAPT , który ma na celu uruchomienie każdego potwierdzenia w jednym teście.
Na stronie głównej projektu napisano:
Właściwe testy jednostkowe powinny zakończyć się niepowodzeniem z dokładnie jednego powodu, dlatego powinieneś używać jednego assert na test jednostkowy.
A także Roy napisał w komentarzach:
Moją wytyczną jest zazwyczaj to, że testujesz jeden logiczny POJĘCIE na test. możesz mieć wiele twierdzeń na tym samym obiekcie . zwykle będą to te same testowane koncepcje.
Myślę, że w niektórych przypadkach potrzebnych jest wiele stwierdzeń (np. Zabezpieczenie ), ale ogólnie staram się tego unikać. Jaka jest Twoja opinia? Podaj przykład z prawdziwego świata, w którym naprawdę wiele twierdzeń jest naprawdę potrzebnych .
źródło
RowTest
(MbUnit) /TestCase
(NUnit) w celu przetestowania różnych zachowań na krawędzi. Użyj odpowiednich narzędzi do pracy! (Niestety, MSTest nie wydaje się jeszcze mieć możliwości testowania w rzędzie.)RowTest
iTestCase
korzystanie ze źródeł danych testowych . Używam prostego pliku CSV z wielkim sukcesem.Odpowiedzi:
Nie uważam, że to musi być zła rzecz , ale uważam, że powinniśmy dążyć do tego, aby w naszych testach były tylko pojedyncze stwierdzenia . Oznacza to, że piszesz o wiele więcej testów, a nasze testy kończą testowaniem tylko jednej rzeczy na raz.
Powiedziawszy to, powiedziałbym, że może połowa moich testów faktycznie ma tylko jedno stwierdzenie. Myślę, że staje się zapachem kodu (testu?) Tylko wtedy, gdy masz około pięciu lub więcej stwierdzeń w teście.
Jak rozwiązujesz wiele twierdzeń?
źródło
Testy powinny zakończyć się niepowodzeniem tylko z jednego powodu, ale nie zawsze oznacza to, że powinna istnieć tylko jedna
Assert
instrukcja. IMHO ważniejsze jest trzymanie się wzorca „ aranżuj, działaj, potwierdzaj ”.Kluczem jest to, że masz tylko jedną akcję, a następnie sprawdzasz wyniki tej akcji za pomocą asercji. Ale jest to „Ułóż, Działaj, Potwierdź, Koniec testu ”. Jeśli masz ochotę kontynuować testowanie, wykonując kolejną czynność, a potem więcej twierdzeń, uczyń to osobnym testem.
Cieszę się, że widzę wiele instrukcji asercji, które stanowią część testowania tej samej akcji. na przykład
lub
Państwo mogli połączyć je w jeden dochodzić, ale to już zupełnie inna sprawa od twierdząc, że pan powinien lub koniecznością . Nie ma poprawy od ich połączenia.
np. pierwszy może być
Ale to nie jest lepsze - komunikat błędu jest mniej konkretny i nie ma innych zalet. Jestem pewien, że możesz pomyśleć o innych przykładach, w których połączenie dwóch lub trzech (lub więcej) twierdzeń w jeden duży warunek boolowski utrudnia czytanie, trudniej je zmienić i trudniej jest ustalić, dlaczego się nie udało. Dlaczego robi to tylko ze względu na regułę?
Uwaga : Kod, który tu piszę, to C # z NUnit, ale zasady będą obowiązywać w innych językach i ramach. Składnia może być również bardzo podobna.
źródło
Assert.IsBetween(10, 100, value)
, moim zdaniem , wydrukiExpected 8 to be between 10 and 100
są lepsze niż dwa osobne stwierdzenia. Z pewnością możesz argumentować, że nie jest to konieczne, ale zwykle warto zastanowić się, czy łatwo jest zredukować do jednego twierdzenia przed zrobieniem ich całego zestawu.Nigdy nie myślałem, że więcej niż jeden twierdzenie było złe.
Ciągle to robię:
Tutaj używam wielu stwierdzeń, aby upewnić się, że złożone warunki można przekształcić w oczekiwany predykat.
Testuję tylko jedną jednostkę (
ToPredicate
metodę), ale obejmuję wszystko, co mogę wymyślić w teście.źródło
Kiedy używam testów jednostkowych do sprawdzania zachowania na wysokim poziomie, absolutnie umieszczam wiele twierdzeń w jednym teście. Oto test, którego faktycznie używam dla jakiegoś awaryjnego kodu powiadomienia. Kod uruchamiany przed testem wprowadza system w stan, w którym uruchomienie głównego procesora powoduje wysłanie alarmu.
Przedstawia warunki, które muszą istnieć na każdym etapie procesu, aby mieć pewność, że kod zachowuje się tak, jak się spodziewam. Jeśli jedno stwierdzenie nie powiedzie się, nie obchodzi mnie, że pozostałe nawet nie uciekną; ponieważ stan systemu nie jest już aktualny, te kolejne stwierdzenia nie powiedziały mi nic cennego. * Jeśli się
assertAllUnitsAlerting()
nie powiedzie, to nie będę wiedział, co zrobić zassertAllNotificationSent()
sukcesem LUB porażką, dopóki nie ustalę, co spowodowało poprzedni błąd i poprawiłem to.(* - W porządku, mogą być przydatne w debugowaniu problemu. Ale już otrzymano najważniejsze informacje, że test się nie powiódł).
źródło
Kolejny powód, dla którego uważam, że wielokrotne twierdzenie w jednej metodzie nie jest złą rzeczą, opisano w następującym kodzie:
W moim teście chcę po prostu przetestować, który
service.process()
zwraca poprawną liczbę wInner
instancjach klasy.Zamiast testować ...
robię
źródło
Assert.notNull
są zbędne, twój test zakończy się niepowodzeniem z NPE, jeśli są zerowe.if
) przejdzie, jeślires
jestnull
Assert.assertEquals(..., service.process().getInner());
, możliwy z wyodrębnionymi zmiennymi, jeśli linia będzie „za długa”Myślę, że istnieje wiele przypadków, w których pisanie wielu twierdzeń jest prawidłowe w ramach reguły, że test powinien zakończyć się niepowodzeniem tylko z jednego powodu.
Na przykład wyobraź sobie funkcję, która analizuje ciąg daty:
Jeśli test się nie powiedzie, to z jednego powodu parsowanie jest niepoprawne. Jeśli argumentujesz, że test może się nie powieść z trzech różnych powodów, IMHO byłby zbyt drobiazgowy w swojej definicji „jednego powodu”.
źródło
Nie znam żadnej sytuacji, w której dobrze byłoby mieć wiele twierdzeń w samej metodzie [Test]. Głównym powodem, dla którego ludzie lubią mieć wiele asercji, jest to, że próbują mieć jedną klasę [TestFixture] dla każdej testowanej klasy. Zamiast tego możesz podzielić swoje testy na więcej klas [TestFixture]. Pozwala to zobaczyć wiele sposobów, w których kod mógł nie zareagować w oczekiwany sposób, zamiast tylko tych, w których pierwsze potwierdzenie nie powiodło się. W ten sposób uzyskuje się testowanie co najmniej jednego katalogu na klasę z dużą ilością klas [TestFixture]. Każda klasa [TestFixture] zostałaby nazwana na podstawie określonego stanu testowanego obiektu. Metoda [SetUp] wprowadzi obiekt do stanu opisanego przez nazwę klasy. Następnie masz wiele metod [Testowych], z których każda potwierdza różne rzeczy, których oczekiwałbyś, że będą prawdziwe, biorąc pod uwagę obecny stan obiektu. Każda metoda [Test] jest nazwana na cześć tego, co twierdzi, z tym wyjątkiem, że może być nazwana na podstawie koncepcji zamiast po prostu odczytu kodu w języku angielskim. Następnie każda implementacja metody [Test] potrzebuje tylko jednego wiersza kodu, w którym coś potwierdza. Kolejną zaletą tego podejścia jest to, że testy są bardzo czytelne, ponieważ staje się całkiem jasne, co testujesz i czego oczekujesz, patrząc na nazwy klas i metod. Skaluje się to również lepiej, gdy zaczniesz zdawać sobie sprawę ze wszystkich małych przypadków krawędziowych, które chcesz przetestować, i gdy znajdziesz błędy. z wyjątkiem tego, że może być nazwany na podstawie koncepcji zamiast po prostu angielskiego odczytu kodu. Następnie każda implementacja metody [Test] potrzebuje tylko jednego wiersza kodu, w którym coś potwierdza. Kolejną zaletą tego podejścia jest to, że testy są bardzo czytelne, ponieważ staje się całkiem jasne, co testujesz i czego oczekujesz, patrząc na nazwy klas i metod. Skaluje się to również lepiej, gdy zaczniesz zdawać sobie sprawę ze wszystkich małych przypadków krawędziowych, które chcesz przetestować, i gdy znajdziesz błędy. z wyjątkiem tego, że może być nazwany na podstawie koncepcji zamiast po prostu angielskiego odczytu kodu. Następnie każda implementacja metody [Test] potrzebuje tylko jednego wiersza kodu, w którym coś potwierdza. Kolejną zaletą tego podejścia jest to, że testy są bardzo czytelne, ponieważ staje się całkiem jasne, co testujesz i czego oczekujesz, patrząc na nazwy klas i metod. Skaluje się to również lepiej, gdy zaczniesz zdawać sobie sprawę ze wszystkich małych przypadków krawędziowych, które chcesz przetestować, i gdy znajdziesz błędy. i czego oczekujesz, patrząc na nazwy klas i metod. Skaluje się to również lepiej, gdy zaczniesz zdawać sobie sprawę ze wszystkich małych przypadków krawędziowych, które chcesz przetestować, i gdy znajdziesz błędy. i czego oczekujesz, patrząc na nazwy klas i metod. Skaluje się to również lepiej, gdy zaczniesz zdawać sobie sprawę ze wszystkich małych przypadków krawędziowych, które chcesz przetestować, i gdy znajdziesz błędy.
Zwykle oznacza to, że ostatni wiersz kodu w metodzie [SetUp] powinien przechowywać wartość właściwości lub wartość zwracaną w zmiennej zmiennej prywatnej [TestFixture]. Następnie możesz twierdzić wiele różnych rzeczy na temat tej zmiennej instancji z różnych metod [Test]. Można również wysunąć twierdzenia na temat tego, jakie różne właściwości testowanego obiektu są ustawione na teraz, gdy jest on w pożądanym stanie.
Czasami po drodze musisz wprowadzić asercje, gdy testujesz obiekt w pożądanym stanie, aby upewnić się, że nie zepsułeś się przed wprowadzeniem obiektu w pożądany stan. W takim przypadku te dodatkowe twierdzenia powinny pojawić się w metodzie [SetUp]. Jeśli coś pójdzie nie tak w metodzie [SetUp], będzie jasne, że coś było nie tak z testem, zanim obiekt osiągnął pożądany stan, który zamierzałeś przetestować.
Innym problemem, na który możesz natknąć się, jest testowanie wyjątku, którego oczekiwano. Może to skusić Cię do nieprzestrzegania powyższego modelu. Jednak nadal można to osiągnąć, wychwytując wyjątek w metodzie [SetUp] i przechowując go w zmiennej instancji. Pozwoli ci to na stwierdzenie różnych rzeczy na temat wyjątku, każdy we własnej metodzie [Test]. Następnie możesz również potwierdzić inne rzeczy dotyczące testowanego obiektu, aby upewnić się, że nie wystąpiły niezamierzone skutki uboczne zgłoszonego wyjątku.
Przykład (byłoby to podzielone na wiele plików):
źródło
[Test]
metody ta klasa jest ponownie tworzona i[SetUp]
metoda jest wykonywana ponownie. To zabija .NET Garbage Collector i powoduje, że testy przebiegają bardzo wolno: ponad 5 minut lokalnie, ponad 20 minut na serwerze kompilacji. Testy 20K powinny trwać około 2-3 minut. W ogóle nie polecałbym tego stylu testowania, szczególnie w przypadku dużych pakietów testowych.Posiadanie wielu asercji w tym samym teście stanowi problem tylko wtedy, gdy test się nie powiedzie. Następnie może być konieczne debugowanie testu lub przeanalizowanie wyjątku, aby dowiedzieć się, które stwierdzenie nie powiedzie się. Z jednym stwierdzeniem w każdym teście zwykle łatwiej jest wskazać, co jest nie tak.
Nie mogę wymyślić scenariusza, w którym naprawdę potrzebnych jest wiele twierdzeń , ponieważ zawsze można je przepisać jako wiele warunków w tym samym stwierdzeniu. Może być jednak preferowane, jeśli na przykład masz kilka kroków w celu weryfikacji danych pośrednich między krokami, zamiast ryzykować, że późniejsze kroki ulegną awarii z powodu złych danych wejściowych.
źródło
Jeśli test się nie powiedzie, nie będziesz wiedział, czy poniższe twierdzenia również się złamią. Często oznacza to, że brakuje cennych informacji, aby ustalić źródło problemu. Moim rozwiązaniem jest użycie jednego potwierdzenia, ale z kilkoma wartościami:
To pozwala mi zobaczyć wszystkie nieudane twierdzenia na raz. Używam kilku wierszy, ponieważ większość IDE wyświetli różnice w łańcuchach w oknie dialogowym porównania obok siebie.
źródło
Jeśli masz kilka stwierdzeń w jednej funkcji testowej, spodziewam się, że będą one bezpośrednio związane z przeprowadzanym testem. Na przykład,
Posiadanie wielu testów (nawet jeśli uważasz, że to prawdopodobnie przesada) nie jest złą rzeczą. Można argumentować, że ważniejsze i najważniejsze testy są ważniejsze. W związku z tym, gdy twierdzisz, upewnij się, że twoje instrukcje potwierdzające są poprawnie umieszczone, zamiast martwić się zbyt wieloma wielokrotnymi twierdzeniami. Jeśli potrzebujesz więcej niż jednego, użyj więcej niż jednego.
źródło
Celem testu jednostkowego jest dostarczenie jak największej ilości informacji na temat tego, co się nie powiedzie, ale również pomoc w dokładnym wskazaniu najbardziej podstawowych problemów. Jeśli wiesz logicznie, że jedno twierdzenie się nie powiedzie, biorąc pod uwagę, że drugie nie powiedzie się lub innymi słowy, istnieje zależność między testem, wówczas sensowne jest rzutowanie ich jako wielu twierdzeń w ramach jednego testu. Ma to tę zaletę, że nie zaśmieca wyników testu oczywistymi błędami, które można by wyeliminować, gdybyśmy wyrzucili pierwsze stwierdzenie w ramach jednego testu. W przypadku, gdy ta relacja nie istnieje, preferowane byłoby oczywiście rozdzielenie tych twierdzeń na poszczególne testy, ponieważ w przeciwnym razie znalezienie tych awarii wymagałoby wielu iteracji testów, aby rozwiązać wszystkie problemy.
Jeśli następnie projektujesz jednostki / klasy w taki sposób, że trzeba by napisać zbyt złożone testy, zmniejsza to obciążenie podczas testowania i prawdopodobnie promuje lepszy projekt.
źródło
Tak, można mieć wiele twierdzeń, o ile test zakończony niepowodzeniem dostarczy wystarczających informacji, aby zdiagnozować awarię. Będzie to zależeć od tego, co testujesz i jakie są tryby awarii.
Nigdy nie uważałem takich sformułowań za pomocne (że klasa powinna mieć jeden powód do zmiany, jest przykładem takiego nieprzydatnego powiedzenia). Rozważmy twierdzenie, że dwa łańcuchy są równe, jest to semantycznie równoważne stwierdzenie, że długość dwóch łańcuchów jest taka sama, a każdy znak w odpowiednim indeksie jest równy.
Możemy uogólnić i powiedzieć, że każdy system wielu asercji może zostać przepisany jako jedno twierdzenie, a każde pojedyncze stwierdzenie może zostać rozłożone na zbiór mniejszych stwierdzeń.
Skup się więc na klarowności kodu i klarowności wyników testu i pozwól, aby kierowało liczbą używanych twierdzeń, a nie odwrotnie.
źródło
Odpowiedź jest bardzo prosta - jeśli testujesz funkcję, która zmienia więcej niż jeden atrybut, tego samego obiektu lub nawet dwóch różnych obiektów, a poprawność funkcji zależy od wyników wszystkich tych zmian, to chcesz potwierdzić że każda z tych zmian została poprawnie wykonana!
Rozumiem logiczną koncepcję, ale odwrotny wniosek mówi, że żadna funkcja nie może nigdy zmienić więcej niż jednego obiektu. Ale z mojego doświadczenia nie jest to możliwe we wszystkich przypadkach.
Przyjmij logiczną koncepcję transakcji bankowej - w większości przypadków wycofanie kwoty z jednego konta bankowego MUSI obejmować dodanie tej kwoty do innego konta. NIGDY nie chcesz rozdzielać tych dwóch rzeczy, tworzą one jednostkę atomową. Możesz chcieć zrobić dwie funkcje (wycofanie / addMoney), a tym samym napisać dwa różne testy jednostkowe - dodatkowo. Ale te dwie czynności muszą odbywać się w ramach jednej transakcji, a Ty chcesz również upewnić się, że transakcja działa. W takim przypadku po prostu nie wystarczy upewnić się, że poszczególne kroki zakończyły się powodzeniem. W teście musisz sprawdzić oba konta bankowe.
Mogą istnieć bardziej złożone przykłady, których nie przetestowałbyś w teście jednostkowym, a przede wszystkim w teście integracji lub testu akceptacji. Ale te granice są płynne, IMHO! Decyzja nie jest tak łatwa, zależy od okoliczności i może osobistych preferencji. Wypłata pieniędzy z jednego konta i dodanie go do innego konta jest nadal bardzo prostą funkcją i zdecydowanie kandydatem do testów jednostkowych.
źródło
To pytanie jest związane z klasycznym problemem równoważenia problemów ze spaghetti i kodami lasagna.
Posiadanie wielu twierdzeń może łatwo wpaść w problem spaghetti, w którym nie masz pojęcia, o co chodzi w teście, ale posiadanie pojedynczego potwierdzenia na test może sprawić, że twoje testowanie będzie równie nieczytelne, gdy będziesz mieć wiele testów w dużej lasagnie, co sprawi, że ustalenie, który test robi to, co niemożliwe. .
Istnieją pewne wyjątki, ale w tym przypadku odpowiedzią jest trzymanie wahadła w środku.
źródło
W ogóle nie zgadzam się z „niepowodzeniem tylko z jednego powodu”. Co ważniejsze, testy są krótkie i brzmią wyraźnie imo.
Nie zawsze jest to jednak możliwe do osiągnięcia, a gdy test jest skomplikowany, (długa) nazwa opisowa i testowanie mniejszej liczby rzeczy ma większy sens.
źródło