Testowanie: deterministyczne czy niedeterministyczne?


16

Czy lepiej mieć któryś z nich

  • Deterministyczny zestaw testów, który powoduje, że te same testy się powiodły
  • Niedeterministyczny zestaw testów, który potencjalnie może obejmować więcej przypadków

?

Przykład: piszesz pakiet testowy, aby przetestować funkcjonalność kontrolera w aplikacji MVC. Kontroler wymaga danych aplikacji z bazy danych jako danych wejściowych podczas testu. Można to zrobić na dwa sposoby:

  • Na stałe wpisujesz, które wiersze z testowej bazy danych są wybrane jako dane wejściowe (np. Wiersz 10 i 412)
  • Używasz generatora liczb losowych do pseudolosowego wybierania danych z bazy danych (dwa wiersze wybrane przez generator liczb losowych)

Pierwszy jest deterministyczny: każde uruchomienie testu dla tej samej wersji kodu powinno dać ten sam wynik. Drugi jest niedeterministyczny: przy każdym uruchomieniu zestawu testów można uzyskać inny wynik. Losowo wybrane dane mogą jednak lepiej reprezentować przypadki krawędzi danych. Czy może to lepiej symulować użytkownika karmiącego nasze kontrolery nieprzewidywalnymi danymi?

Jakie są powody, aby wybierać między sobą?


5
Czasami ten test po prostu się nie udaje. martinfowler.com/articles/nonDeterminism.html

Dzięki za ten link. Mając na uwadze ten artykuł, czułem, że muszę wyjaśnić, że niedeterminizm oznacza w kontekście tego zestawu testów. Ponieważ dane są wybierane losowo z bazy danych, wszystkie dane podawane do kontrolera są domyślnie prawidłowe. Oznacza to, że fałszywe negatywy nie istnieją w zestawie testów, jeśli chodzi o brak determinizmu. W pewien sposób ta losowość symuluje użytkownika wybierającego dane „losowo” do wykorzystania w kontrolerze. To niekoniecznie ten sam niedeterminizm, o którym mówi ten artykuł, prawda?
DCKing,


10
@DCKing: Zastanów się, co się stanie, jeśli test się nie powiedzie. OK, masz błąd. Co teraz? Uruchom go ponownie w trybie debugowania! Gdzie się to udaje! Podobnie jak w przypadku następnych setek uruchomień, a następnie odpisujesz ten problem jako uderzenie promienia kosmicznego. Brak determinizmu w testach brzmi absolutnie niewykonalny. Jeśli czujesz potrzebę pokrycia większej ilości gruntu w testach, pokryj więcej gruntu. Zainicjuj RNG z ustawionym ziarnem i uruchom „test” kilkaset razy z konsekwentnie losowymi wartościami.
Phoshi,

1
(wreszcie dotarłem do maszyny, na której mogłem poprawnie przeszukać Twittera - „ Ten test czasami się nie udaje ” pochodzi z #FiveWordTechHorrors na Twitterze - chciałem go poprawnie przypisać)

Odpowiedzi:


30

Gdy każde uruchomienie pakietu testowego daje możliwość uzyskania innego wyniku, test jest prawie całkowicie bezwartościowy - gdy pakiet pokazuje błąd, masz dużą szansę, że nie będziesz w stanie go odtworzyć, a gdy spróbujesz naprawić błąd, nie można sprawdzić, czy poprawka działa.

Jeśli więc uważasz, że do generowania danych testowych potrzebujesz jakiegoś generatora liczb losowych, upewnij się, że zawsze inicjujesz generator z tym samym ziarnem, lub utrwalasz dane z testu losowego w pliku przed wprowadzeniem go do testu, dzięki czemu możesz ponownie uruchomić test z dokładnie tymi samymi danymi z wcześniejszego uruchomienia. W ten sposób możesz przekształcić dowolny test niedeterministyczny w test deterministyczny.

EDYCJA: Używanie generatora liczb losowych do wybierania niektórych danych testowych jest czasami IMHO oznaką zbyt lenistwa przy wybieraniu dobrych danych testowych. Zamiast rzucać 100 000 losowo wybranych wartości testowych i mieć nadzieję, że to wystarczy, aby przypadkowo odkryć wszystkie poważne błędy, lepiej użyj swojego mózgu, wybierz 10 do 20 „interesujących” przypadków i wykorzystaj je do zestawu testów. Spowoduje to nie tylko lepszą jakość testów, ale także znacznie wyższą wydajność pakietu.


Dziękuję za odpowiedź. Jak oceniasz komentarz do mojego pytania?
DCKing,

1
@DCKing: jeśli naprawdę uważasz, że generator losowy będzie lepszy w wybieraniu dobrych przypadków testowych niż ty (co wątpię), użyj go raz, aby znaleźć kombinacje danych testowych, w których program zawiedzie, i umieść te kombinacje w części „zakodowanej na stałe” pakietu testowego.
Doc Brown

Dzięki jeszcze raz. Zaktualizowałem moją odpowiedź, aby nie dotyczyła tylko aplikacji MVC.
DCKing,

1
W niektórych kontekstach interfejsu użytkownika (na przykład gry pobierające dane z kontrolera) posiadanie programów testowych generujących losowe wprowadzanie klucza może być przydatne do testów warunków skrajnych. Dzięki celowemu wkładowi potrafią odkryć wady, które trudno znaleźć.
Gort the Robot

@StevenBurnap: cóż, ze sposobu, w jaki rozumiem pytanie, myślę, że OP miał na myśli bardziej konwencjonalne testy regresji. Oczywiście, zgadzam się, testy warunków skrajnych są szczególnym przypadkiem, który może być również zależny od sprzętu i skutkować niedeterministycznym zachowaniem, nawet jeśli nie używasz generatora losowego. Jest to coś opisanego w artykule, do którego link dołącza MichaelT w pierwszym komentarzu pod pytaniem. Nawet w testach warunków skrajnych z losowym wejściem można przynajmniej spróbować uczynić to zachowanie bardziej deterministycznym, używając określonego losowego materiału siewnego.
Doc Brown

4

Zarówno deterministyczne, jak i niedeterministyczne mają swoje miejsce

Podzielę je w następujący sposób:

Testy jednostkowe.

Powinny one mieć za każdym razem deterministyczne, powtarzalne testy z dokładnie tymi samymi danymi. Testy jednostkowe towarzyszą określonym, izolowanym sekcjom kodu i powinny je testować w sposób deterministyczny.

Testy wytrzymałościowe i wejściowe.

Mogą stosować podejście niedeterministyczne z następującymi zastrzeżeniami:

  • fakt ten jest wyraźnie nakreślony i wywołany
  • wybrane losowe wartości są rejestrowane i można je ponownie spróbować ręcznie

3

Obie.

Testy deterministyczne i niedeterministyczne mają różne przypadki użycia i różne wartości w zależności od pakietu. Ogólnie niedeterministyczny nie może zapewnić takiej samej precyzji jak test deterministyczny, który powoli przerodził się w „testowanie niedeterministyczne nie przynosi żadnej wartości”. To nieprawda. Mogą być mniej precyzyjne, ale mogą być również znacznie szersze, co ma swoje zalety.

Weźmy przykład: piszesz funkcję, która sortuje listę liczb całkowitych. Jakie przydatne byłyby niektóre deterministyczne testy jednostkowe?

  • Pusta lista
  • Lista zawierająca tylko jeden element
  • Lista zawierająca wszystkie te same elementy
  • Lista z wieloma unikalnymi elementami
  • Lista z wieloma elementami, z których niektóre są duplikatami
  • Lista z NaN, INT_MINiINT_MAX
  • Lista, która jest już częściowo posortowana
  • Lista zawierająca 10 000 000 elementów

A to tylko funkcja sortowania! Jasne, możesz argumentować, że niektóre z nich są niepotrzebne lub że niektóre z nich można wykluczyć za pomocą nieformalnego uzasadnienia. Ale jesteśmy inżynierami i widzieliśmy nieformalne rozumowanie wysadzone w naszą twarz. Wiemy, że nie jesteśmy wystarczająco inteligentni, aby całkowicie zrozumieć systemy, które zbudowaliśmy, lub w pełni utrzymać złożoność w naszych głowach. Dlatego w pierwszej kolejności piszemy testy. Dodanie niedeterministycznych testów mówi po prostu, że niekoniecznie jesteśmy wystarczająco bystrzy, aby z góry poznać wszystkie dobre testy. Rzucając pół losowe dane do swojej funkcji, znacznie łatwiej znajdziesz przypadek, który przegapiłeś.

Oczywiście nie wyklucza to również testów deterministycznych. Testy niedeterministyczne pomagają znaleźć błędy w ogromnych obszarach programu. Jednak po znalezieniu błędów potrzebujesz powtarzalnego sposobu, aby pokazać, że je naprawiłeś. Więc:

  • Użyj niedeterministycznych testów, aby znaleźć błędy w kodzie.
  • Użyj testów deterministycznych, aby zweryfikować poprawki w kodzie.

Zauważ, że oznacza to wiele solidnych porad na temat testów jednostkowych niekoniecznie dotyczy testów niedeterministycznych. Na przykład, że muszą być szybkie. Niskopoziomowe testy właściwości powinny być szybkie, ale niedeterministyczny test, taki jak „symuluj użytkownika losowo klikającego przyciski w witrynie i upewnij się, że nigdy nie pojawi się błąd 500”, powinien sprzyjać kompleksowości zamiast szybkości. Po prostu wykonaj taki test, który uruchamia się niezależnie od procesu kompilacji, aby nie spowalniał rozwoju. Na przykład uruchom go na swoim prywatnym polu testowym.


-1

Naprawdę nie chcesz deterministycznego kontra niedeterministycznego.

To, czego możesz chcieć, to „zawsze taki sam” vs. „nie zawsze taki sam”.

Na przykład, możesz mieć numer kompilacji, który zwiększa się z każdą kompilacją, a gdy chcesz uzyskać liczby losowe, inicjujesz generator liczb losowych z numerem kompilacji jako ziarnem. Więc przy każdej kompilacji wykonujesz testy z różnymi wartościami, co daje większe szanse na znalezienie błędów.

Ale po znalezieniu błędu wystarczy uruchomić test z tym samym numerem kompilacji i jest on odtwarzalny.


1
Lub jeśli nie masz numeru kompilacji do użycia, umieść początkową wartość zarodka na wyjściu uruchomienia testowego, abyś mógł ponownie uruchomić testy z tym samym ziarnem.
RemcoGerlich,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.