Losowe dane w testach jednostkowych?

136

Mam współpracownika, który pisze testy jednostkowe dla obiektów, które wypełniają swoje pola danymi losowymi. Powodem jest to, że daje szerszy zakres testowania, ponieważ będzie testować wiele różnych wartości, podczas gdy normalny test wykorzystuje tylko jedną wartość statyczną.

Podałem mu kilka różnych powodów, z których najważniejsze to:

  • losowe wartości oznaczają, że test nie jest naprawdę powtarzalny (co oznacza również, że jeśli test może się przypadkowo nie powieść, może to zrobić na serwerze kompilacji i przerwać kompilację)
  • jeśli jest to wartość losowa i test się nie powiedzie, musimy a) naprawić obiekt ib) zmusić się do testowania tej wartości za każdym razem, więc wiemy, że działa, ale ponieważ jest losowa, nie wiemy, jaka była wartość

Inny współpracownik dodał:

  • Jeśli testuję wyjątek, losowe wartości nie zapewnią, że test zakończy się w oczekiwanym stanie
  • dane losowe są używane do przepłukiwania systemu i testów obciążeniowych, a nie do testów jednostkowych

Czy ktoś może podać dodatkowe powody, dla których mogę mu dać, aby przestał to robić?

(Lub też, czy jest to akceptowalna metoda pisania testów jednostkowych, a ja i mój drugi współpracownik się mylimy?)

Adam V
źródło
32
„wartości losowe oznaczają, że test nie jest naprawdę powtarzalny”, nieprawda, ponieważ tety będą używać liczb pseudolosowych. Podaj to samo początkowe ziarno, uzyskaj tę samą sekwencję „losowych” testów.
Raedwald,
11
Anegdota: Kiedyś napisałem klasę eksportu CSV i losowe testy ujawniły błąd, gdy znaki kontrolne zostały umieszczone na końcu komórki. Bez testów losowych nie pomyślałbym o dodaniu tego jako przypadku testowego. Czy to zawsze zawodziło? Nie. Czy to doskonały test? Nie. Czy pomogło mi to złapać i naprawić błąd? Tak.
Tyzoid
1
Testy mogą również służyć jako dokumentacja, aby wyjaśnić, kiedy kod oczekuje jako danych wejściowych i czego oczekuje się jako dane wyjściowe. Posiadanie testu z wyraźnymi arbitralnymi danymi może być prostsze i bardziej objaśniające niż kod generujący losowe dane.
splintor
Jeśli test jednostkowy nie powiedzie się z powodu losowo wygenerowanej wartości, a ta wartość nie jest częścią potwierdzenia, powodzenia w debugowaniu testu jednostkowego.
eriksmith200

Odpowiedzi:

72

Jest kompromis. Twój współpracownik naprawdę coś robi, ale myślę, że robi to źle. Nie jestem pewien, czy testowanie całkowicie losowe jest bardzo przydatne, ale z pewnością nie jest nieważne.

Specyfikacja programu (lub jednostki) to hipoteza, że ​​istnieje program, który ją spełnia. Sam program jest więc dowodem tej hipotezy. Testowanie jednostkowe powinno być próbą dostarczenia kontrdowodów, aby obalić, że program działa zgodnie ze specyfikacją.

Teraz możesz ręcznie pisać testy jednostkowe, ale tak naprawdę jest to zadanie mechaniczne. Można to zautomatyzować. Wszystko, co musisz zrobić, to napisać specyfikację, a maszyna może wygenerować wiele testów jednostkowych, które próbują złamać twój kod.

Nie wiem, jakiego języka używasz, ale zobacz tutaj:

Java http://functionaljava.org/

Scala (lub Java) http://github.com/rickynils/scalacheck

Haskell http://www.cs.chalmers.se/~rjmh/QuickCheck/

.NET: http://blogs.msdn.com/dsyme/archive/2008/08/09/fscheck-0-2.aspx

Te narzędzia przyjmą dobrze sformułowaną specyfikację jako dane wejściowe i automatycznie wygenerują dowolną liczbę testów jednostkowych z automatycznie wygenerowanymi danymi. Używają strategii „kurczenia” (które możesz dostosować), aby znaleźć najprostszy możliwy przypadek testowy, który złamie twój kod i upewni się, że dobrze obejmuje skrajne przypadki.

Miłego testowania!

Apocalisp
źródło
1
+1 do tego. ScalaCheck wykonuje fenomenalną pracę generowania zminimalizowanych, losowych danych testowych w powtarzalny sposób.
Daniel Spiewak,
17
To nie jest przypadkowe. To jest arbitralne. Duża różnica :)
Apocalisp
reductiotest.org wydaje się już istnieć, a Google nie wskazał mi nigdzie indziej. Masz jakiś pomysł, gdzie to jest teraz?
Raedwald,
Jest teraz częścią funkcjonalnej biblioteki Java. Edytowano link. Ale użyłbym Scalachecka do testowania kodu Java.
Apocalisp
ScalaCheck został przeniesiony do GitHub. Zaktualizowany link w odpowiedzi. Przydaje się również w Javie, nie tylko w Scali. (Stary link to code.google.com/p/scalacheck )
RobertB
38

Ten rodzaj testów nazywa się testem Małpy . Po prawidłowym wykonaniu może wypalić owady z naprawdę ciemnych zakamarków.

Aby rozwiać obawy dotyczące odtwarzalności: właściwym sposobem podejścia do tego zagadnienia jest zapisanie nieudanych wpisów testowych, wygenerowanie testu jednostkowego, który sprawdza całą rodzinę określonego błędu; i uwzględnij w teście jednostkowym jedno określone wejście (z danych losowych), które spowodowało początkową awarię.

Srebrny Smok
źródło
26

Jest tutaj dom pośredni, który ma pewne zastosowanie, którym jest zasianie twojego PRNG stałą. To pozwala na generowanie „losowych” danych, które są powtarzalne.

Osobiście uważam, że są miejsca, w których (stałe) dane losowe są przydatne w testowaniu - po tym, jak myślisz, że wykonałeś wszystkie dokładnie przemyślane zakręty, użycie bodźców z PRNG może czasami znaleźć inne rzeczy.

Will Dean
źródło
4
Widziałem, jak to działa dobrze w systemie, który miał wiele blokad i wątków. Ziarno „losowe” było zapisywane do pliku przy każdym uruchomieniu, a następnie, jeśli przebieg nie powiódł się, mogliśmy określić ścieżkę, którą obrał kod i napisać odręczny test jednostkowy dla tego przypadku, który przegapiliśmy.
Ian Ringrose
Co oznacza PRNG?
systemovich
Generator liczb pseudolosowych
Will Dean
16

W książce Beautiful Code znajduje się rozdział zatytułowany „Beautiful Tests”, w którym omawia strategię testowania algorytmu wyszukiwania binarnego . Jeden akapit nosi tytuł „Losowe akty testowania”, w którym tworzy losowe tablice, aby dokładnie przetestować algorytm. Możesz przeczytać część tego w Internecie w Książkach Google, strona 95 , ale warto ją mieć.

Zasadniczo pokazuje to tylko, że generowanie losowych danych do testów jest realną opcją.

mreggen
źródło
16

Jestem zwolennikiem testów losowych i piszę je. Jednak to, czy są one odpowiednie w określonym środowisku kompilacji i które zestawy testów powinny być zawarte, jest bardziej zniuansowanym pytaniem.

Uruchomione lokalnie (np. Przez całą noc na swoim dev boxie), losowe testy wykazały błędy zarówno oczywiste, jak i niejasne. Te niejasne są na tyle tajemnicze, że myślę, że losowe testy były naprawdę jedynym realistycznym sposobem ich usunięcia. W ramach testu wziąłem jeden trudny do znalezienia błąd wykryty za pomocą testów losowych i poleciłem pół tuzinowi programistów cracków, aby przejrzeli funkcję (około tuzina linii kodu), w której wystąpił. Nikt nie był w stanie tego wykryć.

Wiele z twoich argumentów przeciwko randomizowanym danym jest posmakiem „testu nie da się odtworzyć”. Jednak dobrze napisany test losowy wychwyci ziarno użyte do uruchomienia zrandomizowanego ziarna i wyprowadzi je w przypadku niepowodzenia. Oprócz możliwości ręcznego powtórzenia testu, pozwala to w prosty sposób stworzyć nowy test, który będzie testował konkretny problem, poprzez zakodowanie materiału siewnego dla tego testu. Oczywiście, prawdopodobnie przyjemniej jest ręcznie zakodować jawny test obejmujący ten przypadek, ale lenistwo ma swoje zalety, a to nawet pozwala zasadniczo automatycznie generować nowe przypadki testowe z powodującego niepowodzenie materiału siewnego.

Jedyną kwestią, o której nie mogę debatować, jest to, że psuje systemy kompilacji. Większość testów kompilacji i testów ciągłej integracji oczekuje, że testy będą za każdym razem robić to samo. Tak więc test, który przypadkowo się nie powiedzie, spowoduje chaos, losowe niepowodzenie i wskazanie palcami zmian, które były nieszkodliwe.

W takim razie rozwiązaniem jest nadal uruchamianie testów losowych w ramach testów kompilacji i testów CI, ale uruchamianie ich ze stałym materiałem inicjującym dla ustalonej liczby iteracji . Dlatego test zawsze robi to samo, ale nadal bada kilka przestrzeni wejściowych (jeśli uruchomisz go dla wielu iteracji).

Lokalnie, np. Podczas zmiany danej klasy, możesz uruchomić ją w celu uzyskania większej liczby iteracji lub z innymi ziarnami. Jeśli testy losowe kiedykolwiek staną się bardziej popularne, możesz sobie nawet wyobrazić określony zestaw testów, o których wiadomo, że są losowe, które można by uruchomić z różnymi zalążkami (a więc z rosnącym zasięgiem w czasie) i gdzie awarie nie oznaczałyby tego samego ponieważ deterministyczne systemy CI (tj. przebiegi nie są kojarzone 1: 1 ze zmianami kodu, więc nie wskazuje się palcem na konkretną zmianę, gdy coś się nie powiedzie).

Wiele można powiedzieć o testach losowych, szczególnie dobrze napisanych, więc nie odrzucaj ich zbyt szybko!

BeeOnRope
źródło
14

Jeśli robisz TDD, argumentowałbym, że dane losowe to doskonałe podejście. Jeśli twój test jest napisany ze stałymi, możesz zagwarantować, że kod działa tylko dla określonej wartości. Jeśli test losowo kończy się niepowodzeniem z serwera kompilacji, prawdopodobnie problem dotyczy sposobu, w jaki test został napisany.

Losowe dane pomogą zagwarantować, że przyszłe refaktoryzacje nie będą opierać się na magicznej stałej. W końcu, jeśli twoje testy są twoją dokumentacją, to czy obecność stałych nie oznacza, że ​​musi działać tylko dla tych stałych?

Przesadzam, ale wolę wstawiać do testu losowe dane jako znak, że „wartość tej zmiennej nie powinna wpływać na wynik tego testu”.

Powiem jednak, że jeśli używasz zmiennej losowej, a następnie rozwidlaj swój test na podstawie tej zmiennej, to jest to zapach.

Jimmy Bosse
źródło
10

Jedną zaletą dla kogoś, kto ogląda testy, jest to, że arbitralne dane nie są oczywiście ważne. Widziałem zbyt wiele testów, które obejmowały dziesiątki fragmentów danych i może być trudno powiedzieć, co ma być w ten sposób, a co tak się dzieje. Np. Jeśli metoda sprawdzania poprawności adresu jest testowana z określonym kodem pocztowym, a wszystkie inne dane są losowe, możesz być prawie pewien, że kod pocztowy jest jedyną ważną częścią.

Trystan Spangler
źródło
9
  • jeśli jest to wartość losowa i test się nie powiedzie, musimy a) naprawić obiekt ib) zmusić się do testowania tej wartości za każdym razem, więc wiemy, że działa, ale ponieważ jest losowa, nie wiemy, jaka była wartość

Jeśli Twój przypadek testowy nie rejestruje dokładnie tego, co testuje, być może musisz ponownie zakodować przypadek testowy. Zawsze chcę mieć dzienniki, do których mogę się odwoływać w przypadku przypadków testowych, aby dokładnie wiedzieć, co spowodowało niepowodzenie, niezależnie od tego, czy używam danych statycznych, czy losowych.

EBGreen
źródło
9

Twój współpracownik przeprowadza testy fuzz , chociaż o tym nie wie. Są szczególnie cenne w systemach serwerowych.

Robert Gould
źródło
2
ale czy nie różni się to zasadniczo od testów jednostkowych? i zrobione w innym czasie?
endolit
1
@endolith nie ma prawa fizyki, które zmusza cię do przeprowadzania określonych testów w określonym czasie
user253751
1
@immibis Istnieją jednak dobre powody, aby wykonywać określone testy w określonych momentach. Nie przeprowadzasz baterii testów jednostkowych za każdym razem, gdy użytkownik kliknie przycisk „OK”.
endolith
5

Czy możesz raz wygenerować losowe dane (mam na myśli dokładnie raz, a nie raz na przebieg testu), a następnie użyć ich we wszystkich późniejszych testach?

Zdecydowanie widzę wartość w tworzeniu losowych danych w celu testowania przypadków, o których nie pomyślałeś, ale masz rację, posiadanie testów jednostkowych, które mogą przypadkowo przejść lub zawodzić, jest złą rzeczą.

Wyjęty spod prawa programista
źródło
5

Powinieneś zadać sobie pytanie, jaki jest cel twojego testu.
Testy jednostkowe dotyczą weryfikacji logiki, przepływu kodu i interakcji obiektów. Korzystanie z wartości losowych ma na celu osiągnięcie innego celu, co ogranicza skupienie i prostotę testu. Jest to dopuszczalne ze względu na czytelność (generowanie UUID, identyfikatorów, kluczy itp.).
Szczególnie w przypadku testów jednostkowych nie mogę sobie przypomnieć, kiedy ta metoda skutecznie znajdowała problemy. Ale widziałem wiele problemów związanych z determinizmem (w testach), próbujących być sprytnym z przypadkowymi wartościami, a głównie z przypadkowymi datami.
Testowanie Fuzz to poprawne podejście do testów integracyjnych i testów end-to-end .

Ohad Bruker
źródło
Dodam, że używanie losowych danych wejściowych do fuzzingu jest kiepskim substytutem fuzzingu sterowanego pokryciem, gdy jest to możliwe.
gobenji
1

Jeśli do testów używasz losowych danych wejściowych, musisz je rejestrować, aby zobaczyć, jakie są wartości. W ten sposób, jeśli jest jakaś sprawa krawędź można natknąć, to może napisać test, aby go powtórzyć. Słyszałem te same powody od ludzi, którzy nie używają przypadkowych danych wejściowych, ale kiedy masz wgląd w rzeczywiste wartości używane dla konkretnego przebiegu testowego, nie stanowi to aż tak dużego problemu.

Pojęcie „dowolnych” danych jest również bardzo przydatne jako sposób oznaczenia czegoś, co nie jest ważne. Mamy kilka testów akceptacyjnych, które przychodzą na myśl, gdy istnieje wiele danych dotyczących hałasu, które nie mają związku z bieżącym testem.

Craigb
źródło
0

W zależności od obiektu / aplikacji losowe dane miałyby miejsce w testach obciążenia. Myślę, że ważniejsze byłoby użycie danych, które jawnie testują warunki brzegowe danych.

EBGreen
źródło
0

Właśnie dzisiaj w to wpadliśmy. Chciałem pseudolosowy (żeby pod względem rozmiaru wyglądał jak skompresowane dane audio). ZROBIŁEM, że chciałem też mieć determinizm . rand () był inny na OSX niż na Linuksie. A jeśli nie zasieję ponownie, może się to zmienić w dowolnym momencie. Więc zmieniliśmy go na deterministyczny, ale nadal pseudolosowy: test jest powtarzalny, tak samo jak przy użyciu gotowych danych (ale wygodniej jest napisać).

To NIE było testowanie przez jakąś przypadkową brutalną siłę poprzez ścieżki kodu. Na tym polega różnica: wciąż deterministyczna, wciąż powtarzalna, wciąż wykorzystująca dane, które wyglądają jak rzeczywiste dane wejściowe, aby przeprowadzić zestaw interesujących sprawdzeń przypadków skrajnych w złożonej logice. Wciąż testy jednostkowe.

Czy to nadal się kwalifikuje, jest losowe? Porozmawiajmy przy piwie. :-)

Tim James
źródło
0

Mogę wyobrazić sobie trzy rozwiązania problemu danych testowych:

  • Testuj ze stałymi danymi
  • Testuj z losowymi danymi
  • Wygeneruj losowe dane raz , a następnie użyj ich jako stałych danych

Poleciłbym wykonanie wszystkich powyższych czynności . Oznacza to, że napisz powtarzalne testy jednostkowe, używając zarówno niektórych przypadków skrajnych opracowanych za pomocą mózgu, jak i niektórych losowych danych, które generujesz tylko raz. Następnie należy napisać zestaw testów z randomizacją, które uruchomić także .

Nigdy nie należy oczekiwać, że testy randomizowane wychwycą coś, czego brakuje w powtarzalnych testach. Powinieneś dążyć do pokrycia wszystkiego powtarzalnymi testami i traktować testy losowe jako bonus. Jeśli coś znajdą, powinno to być coś, czego nie można było racjonalnie przewidzieć; prawdziwy dziwak.

Tom W.
źródło
-2

Jak twój facet może ponownie przeprowadzić test, kiedy nie udało mu się sprawdzić, czy go naprawił? Tzn. Traci powtarzalność testów.

Chociaż myślę, że wyrzucanie losowych danych podczas testów prawdopodobnie ma jakąś wartość, jak wspomniano w innych odpowiedziach, jest to bardziej objęte tematem testowania obciążenia niż cokolwiek innego. Jest to w zasadzie praktyka „testowania po nadziei”. Myślę, że w rzeczywistości twój facet po prostu nie myśli o tym, co próbuje przetestować, i nadrabia ten brak myśli, mając nadzieję, że przypadkowość ostatecznie uwięzi jakiś tajemniczy błąd.

Więc argument, którego użyłbym z nim jest taki, że jest leniwy. Innymi słowy, jeśli nie poświęci czasu na zrozumienie, co próbuje przetestować, prawdopodobnie oznacza to, że tak naprawdę nie rozumie kodu, który pisze.

Greg Whitfield
źródło
3
Możliwe jest rejestrowanie losowych danych lub losowego ziarna, aby można było odtworzyć test.
cbp