Czy twierdzenia lub testy jednostkowe są ważniejsze?

51

Zarówno twierdzenia, jak i testy jednostkowe służą jako dokumentacja bazy kodu i sposób wykrywania błędów. Główne różnice polegają na tym, że funkcja działa jako kontrola poczytalności i widzi rzeczywiste dane wejściowe, podczas gdy testy jednostkowe działają na określonych symulowanych danych wejściowych i są testami na podstawie jednej dobrze zdefiniowanej „właściwej odpowiedzi”. Jakie są względne zalety stosowania twierdzeń w porównaniu z testami jednostkowymi jako głównego sposobu weryfikacji poprawności? Jak myślisz, co powinno zostać podkreślone mocniej?

dsimcha
źródło
4
Bardzo podoba mi się ten pomysł ... tak bardzo, że uczyniłem ten temat zadaniem dla mojego testu oprogramowania i kursu zapewnienia jakości. :-)
Macneil
Kolejne pytanie brzmi: czy powinieneś przetestować swoje twierdzenia w jednostce? ;)
mojuba,

Odpowiedzi:

43

Aserty są przydatne do informowania o wewnętrznym stanie programu . Na przykład, że twoje struktury danych mają prawidłowy stan, np. Że Timestruktura danych nie przechowuje wartości 25:61:61. Warunki sprawdzane przez stwierdzenia to:

  • Warunki wstępne, które gwarantują, że dzwoniący dotrzyma umowy,

  • Warunki dodatkowe, które gwarantują, że odbiorca dotrzymuje kontraktu, oraz

  • Niezmienniki, które zapewniają, że struktura danych zawsze ma pewną właściwość po powrocie funkcji. Niezmiennik jest warunkiem, który jest warunkiem wstępnym i wtórnym.

Testy jednostkowe są przydatne, aby poinformować Cię o zewnętrznym zachowaniu modułu . Możesz Stackmieć spójny stan po push()wywołaniu metody, ale jeśli rozmiar stosu nie wzrośnie o trzy po wywołaniu trzykrotnie, to jest to błąd. (Na przykład trywialny przypadek, w którym niepoprawna push()implementacja sprawdza tylko twierdzenia i kończy działanie).

Ściśle mówiąc, główna różnica między asertami a testami jednostkowymi polega na tym, że testy jednostkowe zawierają dane testowe (wartości umożliwiające uruchomienie programu), podczas gdy twierdzenia nie. Oznacza to, że możesz wykonać testy jednostkowe automatycznie, podczas gdy nie możesz powiedzieć tego samego o twierdzeniach. Na potrzeby tej dyskusji założyłem, że mówisz o wykonywaniu programu w kontekście testów funkcji wyższego rzędu (które wykonują cały program i nie sterują modułami jak testy jednostkowe). Jeśli nie mówimy o automatycznych testach funkcji jako sposobie „zobaczenia rzeczywistych danych wejściowych”, to oczywiście wartość leży w automatyzacji, a zatem testy jednostkowe by wygrały. Jeśli mówisz o tym w kontekście (automatycznych) testów funkcji, zobacz poniżej.

Testowane elementy mogą się pokrywać. Na przykład Stackwarunek końcowy może faktycznie twierdzić, że rozmiar stosu wzrasta o jeden. Są jednak granice tego, co można wykonać w tym stwierdzeniu: czy powinno to również sprawdzić, czy element górny jest właśnie dodany?

W obu przypadkach celem jest poprawa jakości. W przypadku testów jednostkowych celem jest znalezienie błędów. W przypadku stwierdzeń celem jest ułatwienie debugowania poprzez obserwowanie niepoprawnych stanów programu natychmiast po ich wystąpieniu.

Zauważ, że żadna technika nie weryfikuje poprawności. W rzeczywistości, jeśli przeprowadzisz testy jednostkowe w celu sprawdzenia, czy program jest poprawny, prawdopodobnie opracujesz nieciekawy test, o którym wiesz, że zadziała. To efekt psychologiczny: zrobisz wszystko, aby osiągnąć swój cel. Jeśli Twoim celem jest znalezienie błędów, twoje działania to odzwierciedlą.

Oba są ważne i mają swoje własne cele.

[Jako ostatnia uwaga na temat twierdzeń: Aby uzyskać jak największą wartość, musisz używać ich we wszystkich krytycznych punktach programu, a nie kilku kluczowych funkcjach. W przeciwnym razie pierwotne źródło problemu mogło zostać zamaskowane i trudne do wykrycia bez wielu godzin debugowania.]

Macneil
źródło
7

Mówiąc o asercjach, pamiętaj, że można je wyłączyć jednym naciśnięciem przycisku.

Przykład bardzo złego stwierdzenia:

char *c = malloc(1024);
assert(c != NULL);

Dlaczego to takie złe? Ponieważ sprawdzanie błędów nie jest wykonywane, jeśli to twierdzenie zostanie pominięte z powodu zdefiniowania czegoś takiego jak NDEBUG.

Test jednostkowy (prawdopodobnie) po prostu spowodowałby błąd w powyższym kodzie. Jasne, że wykonał swoją pracę, mówiąc ci, że coś poszło nie tak, czy to zrobiło? Jakie jest prawdopodobieństwo malloc()niepowodzenia w teście?

Asercje służą do debugowania, gdy programista musi sugerować, że żadne „normalne” zdarzenie nie spowodowałoby uruchomienia asercji. malloc()upadek jest rzeczywiście normalnym wydarzeniem, dlatego nigdy nie należy go twierdzić.

Istnieje wiele innych przypadków, w których stosowane są twierdzenia zamiast odpowiedniego postępowania z rzeczami, które mogą pójść nie tak. To dlatego twierdzenia cieszą się złą reputacją i dlaczego języki takie jak Go nie uwzględniają ich.

Testy jednostkowe mają na celu stwierdzenie, kiedy coś, co zmieniłeś, zepsuło coś innego. Zostały one zaprojektowane tak, aby uchronić Cię (w większości przypadków) przed przeglądaniem wszystkich funkcji programu (jednak testery ważne w wersjach) za każdym razem, gdy tworzysz kompilację.

Naprawdę nie ma żadnej wyraźnej korelacji między nimi, poza tym, że obie mówią, że coś poszło nie tak. Pomyśl o asercie jako o punkcie przerwania w czymś, nad czym pracujesz, bez konieczności używania debuggera. Pomyśl o teście jednostkowym jako o czymś, co powie ci, jeśli złamałeś coś, nad czym nie pracujesz.

Tim Post
źródło
3
Dlatego twierdzenia zawsze mają być prawdziwymi stwierdzeniami, a nie testowaniem błędów.
Dominique McDonnell,
@DominicMcDonnell Cóż, stwierdzenia „powinny być prawdziwe”. Czasami twierdzę, że omijam dziwactwa kompilatora, takie jak niektóre wersje gcc z wbudowanym błędnym abs (). Ważną rzeczą do zapamiętania jest to, że kompilacje produkcyjne i tak powinny je wyłączyć .
Tim Post
I tutaj myślę kod produkcyjny znajduje się miejsce, które potrzebuje twierdzi najbardziej , bo to w produkcji, gdzie dostaniesz wejść, że nie sądziłem możliwe. Produkcja jest miejscem, które usuwa wszystkie najcięższe błędy.
Frank Shearar,
@Frank Shearar, bardzo prawda. Nadal powinieneś mocno zawieść przy najwcześniejszym wykrytym stanie błędu w produkcji. Pewnie użytkownicy będą narzekać, ale to jedyny sposób, aby upewnić się, że błędy zostaną naprawione. I o wiele lepiej jest, jeśli bla wynosił 0, niż wyjątek pamięci dla dereferencji null kilka wywołań funkcji później.
Dominique McDonnell,
dlaczego nie lepiej radzić sobie z typowymi, ale często rzadkimi (w oczach użytkownika) problemami, niż twierdzić, że nigdy nie powinny się zdarzyć? Nie zdaję sobie sprawy z tej mądrości. Jasne, zapewnij, że generator liczb pierwszych zwraca liczbę pierwszą, co wymaga trochę dodatkowej pracy, co jest oczekiwane w kompilacjach debugowania. Twierdzenie, że język może natywnie przetestować, jest po prostu głupie. Nie zawijanie tych testów innym przełącznikiem, który można wyłączyć kilka miesięcy po wydaniu, jeszcze bardziej głupie.
Tim Post
5

Oba narzędzia służą do poprawy ogólnej jakości budowanego systemu. Wiele zależy od używanego języka, rodzaju tworzonej aplikacji i miejsca, w którym najlepiej spędzić czas. Nie wspominając o tym, że masz na ten temat kilka szkół.

Na początek, jeśli używasz języka bez assertsłowa kluczowego, nie możesz używać asercji (przynajmniej nie tak, jak tutaj mówimy). Przez długi czas Java nie miała assertsłowa kluczowego, a wiele języków nadal nie ma. Testy jednostkowe stają się wtedy znacznie ważniejsze. W niektórych językach asercje są uruchamiane tylko po ustawieniu flagi (ponownie tutaj w Javie). Gdy zabezpieczenia nie zawsze są dostępne, nie jest to bardzo przydatna funkcja.

Istnieje szkoła myślenia, która mówi, że „twierdząc” o czymś, równie dobrze możesz napisać if/ throwznaczący blok wyjątków. Ten proces myślowy pochodzi z wielu twierdzeń umieszczonych na początku metody, aby upewnić się, że wszystkie wartości mieszczą się w granicach. Testowanie warunków wstępnych jest bardzo ważną częścią posiadania oczekiwanego warunku końcowego.

Testy jednostkowe to dodatkowy kod, który należy napisać i utrzymywać. Dla wielu jest to wadą. Jednak przy obecnych uprawach ram testów jednostkowych istnieje możliwość wygenerowania większej liczby warunków testowych przy stosunkowo niewielkim kodzie. Testy sparametryzowane i „teorie” przeprowadzą ten sam test z dużą liczbą próbek danych, które mogą wykryć niektóre trudne do znalezienia błędy.

Osobiście uważam, że dzięki testom jednostkowym mam większy przebieg niż twierdzenie, ale dzieje się tak z powodu platform, które rozwijam przez większość czasu (Java / C #). Inne języki mają bardziej niezawodną obsługę asercji, a nawet „Design by Contract” (patrz poniżej), aby zapewnić jeszcze więcej gwarancji. Gdybym pracował z jednym z tych języków, mógłbym użyć DBC bardziej niż testowania jednostkowego.

http://en.wikipedia.org/wiki/Design_by_contract#Languages_with_native_support

Berin Loritsch
źródło