Mam klasę, która ma generować losowe hasło o długości, która jest również losowa, ale ograniczona do określonych między minimalną a maksymalną długością.
Tworzę testy jednostkowe i natknąłem się na interesującą małą przeszkodę w tej klasie. Cała idea testu jednostkowego polega na tym, że powinien on być powtarzalny. Jeśli uruchomisz test sto razy, powinien on dać te same wyniki sto razy. Jeśli zależysz od jakiegoś zasobu, który może, ale nie musi, znajdować się w początkowym stanie, którego oczekujesz, masz za zadanie kpić z danego zasobu, aby upewnić się, że Twój test jest zawsze powtarzalny.
Ale co z przypadkami, w których SUT ma generować nieokreślony wynik?
Jeśli poprawię minimalną i maksymalną długość na tę samą wartość, mogę łatwo sprawdzić, czy wygenerowane hasło ma oczekiwaną długość. Ale jeśli podam zakres akceptowalnych długości (powiedzmy 15-20 znaków), wtedy masz problem, że możesz uruchomić test sto razy i uzyskać 100 przebiegów, ale przy 101 biegu możesz odzyskać ciąg 9 znaków.
W przypadku klasy haseł, która jest dość prosta w swoim rdzeniu, nie powinna stanowić dużego problemu. Ale pomyślałem o ogólnym przypadku. Jaka strategia jest zwykle akceptowana jako najlepsza do wyboru, gdy mamy do czynienia z SUT, które generują nieokreślony wynik z projektu?
źródło
Odpowiedzi:
Wyjście „niedeterministyczne” powinno mieć sposób, by stać się deterministyczne na potrzeby testów jednostkowych. Jednym ze sposobów radzenia sobie z losowością jest umożliwienie wymiany losowego silnika. Oto przykład (PHP 5.3+):
Możesz stworzyć specjalną testową wersję funkcji, która zwraca dowolną sekwencję liczb, które chcesz upewnić się, że test jest w pełni powtarzalny. W prawdziwym programie możesz mieć domyślną implementację, która może być rezerwowa, jeśli nie zostanie zastąpiona.
źródło
Rzeczywiste hasło wyjściowe może nie zostać określone za każdym razem, gdy metoda jest wykonywana, ale nadal będzie miało określone funkcje, które można przetestować, takie jak minimalna długość, znaki mieszczące się w określonym zestawie znaków itp.
Możesz także przetestować, czy procedura zwraca za każdym razem określony wynik, za każdym razem zapełniając generator haseł tą samą wartością.
źródło
Test pod kątem „umowy”. Gdy metody są zdefiniowane jako „generuje hasła o długości od 15 do 20 znaków z az”, przetestuj to w ten sposób
Dodatkowo możesz wyodrębnić generację, aby wszystko, co na niej polegało, mogło zostać przetestowane przy użyciu innej „statycznej” klasy generatora
źródło
Masz
Password generator
i potrzebujesz losowego źródła.Jak powiedziałeś w pytaniu, a
random
tworzy niedeterministyczny wynik, ponieważ jest to stan globalny . Oznacza to, że uzyskuje dostęp do czegoś poza systemem, aby wygenerować wartości.Nigdy nie możesz pozbyć się czegoś takiego dla wszystkich swoich klas, ale możesz oddzielić generowanie haseł do tworzenia losowych wartości.
Jeśli skonstruujesz taki kod, możesz wyśmiewać jego
RandomSource
testy.Nie będziesz w stanie w 100% przetestować,
RandomSource
ale możesz zastosować do niego sugestie dotyczące testowania wartości z tego pytania (na przykład testowanie, którerand->(1,26);
zawsze zwraca liczbę od 1 do 26).źródło
W przypadku fizyki cząstek elementarnych Monte Carlo napisałem „testy jednostkowe” {*}, które odwołują się do niedeterministycznej rutyny ze wstępnie ustawionym losowym ziarnem , a następnie przeprowadzam statystyczną liczbę razy i sprawdzam, czy nie występują ograniczenia (poziomy energii) powyżej energii wejściowej musi być niedostępny, wszystkie przejścia muszą wybrać jakiś poziom itp.) oraz regresje względem wcześniej zarejestrowanych wyników.
{*} Taki test narusza zasadę „wykonaj szybki test” w przypadku testów jednostkowych, dlatego możesz lepiej je scharakteryzować w inny sposób: na przykład testy akceptacyjne lub testy regresyjne. Mimo to korzystałem z mojej platformy do testów jednostkowych.
źródło
Muszę się nie zgodzić z przyjętą odpowiedzią z dwóch powodów:
(Zauważ, że może to być dobra odpowiedź w wielu okolicznościach, ale nie we wszystkich, a może nie w większości).
Co mam przez to na myśli? Przez nadmierne dopasowanie rozumiem typowy problem testowania statystycznego: nadmierne dopasowanie ma miejsce, gdy testujesz algorytm stochastyczny w stosunku do nadmiernie ograniczonego zestawu danych. Jeśli następnie wrócisz i dopracujesz algorytm, domyślnie sprawisz, że będzie on bardzo dobrze pasował do danych treningowych (przypadkowo dopasujesz swój algorytm do danych testowych), ale wszystkie inne dane mogą wcale nie być (ponieważ nigdy nie testujesz go) .
(Nawiasem mówiąc, zawsze jest to problem, który czai się przy testowaniu jednostkowym. Dlatego dobre testy są kompletne lub przynajmniej reprezentatywne dla danej jednostki, i ogólnie jest to trudne.)
Jeśli uczynisz swoje testy deterministycznymi, umożliwiając podłączenie generatora liczb losowych, zawsze testujesz na tym samym bardzo małym i (zwykle) niereprezentatywnym zestawie danych. To wypacza twoje dane i może prowadzić do stronniczości w twojej funkcji.
Drugi punkt, niewykonalność, powstaje, gdy nie masz żadnej kontroli nad zmienną stochastyczną. Zwykle nie dzieje się tak w przypadku generatorów liczb losowych (chyba że potrzebujesz „prawdziwego” źródła losowego), ale może się zdarzyć, gdy stochastycy wkradną się do twojego problemu innymi sposobami. Na przykład podczas testowania współbieżnego kodu: warunki wyścigu są zawsze stochastyczne, nie można (łatwo) uczynić ich deterministycznymi.
Jedynym sposobem na zwiększenie zaufania w takich przypadkach jest przeprowadzenie wielu testów . Spłucz, spłucz, powtórz. Zwiększa to pewność do pewnego poziomu (w którym momencie kompromis dla dodatkowych testów staje się znikomy).
źródło
Masz tutaj wiele obowiązków. Testy jednostkowe, a zwłaszcza TDD, świetnie nadają się do podkreślenia tego rodzaju rzeczy.
Obowiązki to:
1) Generator liczb losowych. 2) Formatator hasła.
Formatyzator haseł używa generatora liczb losowych. Wstaw generator do formatera za pomocą jego konstruktora jako interfejsu. Teraz możesz w pełni przetestować generator liczb losowych (test statystyczny) i przetestować formatyzator, wstrzykując wyśmiewany generator liczb losowych.
Nie tylko dostajesz lepszy kod, ale także lepsze testy.
źródło
Jak już wspomniano inni, testujesz ten kod, usuwając losowość.
Możesz również chcieć mieć test wyższego poziomu, który pozostawia generator liczb losowych na miejscu, testuje tylko kontrakt (długość hasła, dozwolone znaki, ...), a w przypadku awarii zrzuca wystarczającą ilość informacji, aby umożliwić odtworzenie systemu stan w jednym przypadku, w którym losowy test nie powiódł się.
Nie ma znaczenia, że sam test nie jest powtarzalny - o ile tylko znajdziesz przyczynę, dla której ten raz się nie powiódł.
źródło
Wiele trudności w testowaniu jednostkowym staje się trywialnych po przefakturowaniu kodu w celu zerwania zależności. Baza danych, system plików, użytkownik lub, w twoim przypadku, źródło losowości.
Innym sposobem patrzenia jest to, że testy jednostkowe mają odpowiedzieć na pytanie „czy ten kod robi to, co zamierzam?”. W twoim przypadku nie wiesz, co zamierzasz zrobić, ponieważ kod jest niedeterministyczny.
Mając to na uwadze, podziel swoją logikę na małe, łatwe do zrozumienia, łatwe do przetestowania w izolacji części. W szczególności tworzysz odrębną metodę (lub klasę!), Która pobiera źródło losowości jako dane wejściowe i tworzy hasło jako dane wyjściowe. Ten kod jest wyraźnie deterministyczny.
W teście jednostkowym za każdym razem podajesz to samo niezbyt losowe dane wejściowe. W przypadku bardzo małych losowych strumieni po prostu zakoduj wartości w swoim teście. W przeciwnym razie zapewnij stały test RNG w teście.
Na wyższym poziomie testowania (nazwij to „akceptacją” lub „integracją” lub czymkolwiek innym), pozwolisz, aby kod działał z prawdziwym losowym źródłem.
źródło
Większość powyższych odpowiedzi wskazuje, że najlepiej jest wyśmiewać generator liczb losowych, ale po prostu użyłem wbudowanej funkcji mt_rand. Zezwolenie na kpowanie oznaczałoby przepisanie klasy, aby wymagała wstrzyknięcia generatora liczb losowych w czasie budowy.
A przynajmniej tak myślałem!
Jedną z konsekwencji dodania przestrzeni nazw jest to, że drwiny wbudowane w funkcje PHP przeszły z niezwykle trudnej do banalnie prostej. Jeśli SUT znajduje się w danej przestrzeni nazw, wystarczy, że zdefiniujesz własną funkcję mt_rand w teście jednostkowym w tej przestrzeni nazw i będzie ona używana zamiast wbudowanej funkcji PHP na czas trwania testu.
Oto sfinalizowany pakiet testowy:
Pomyślałem, że o tym wspomnę, ponieważ zastąpienie wewnętrznych funkcji PHP to kolejne zastosowanie dla przestrzeni nazw, które po prostu mi się nie przydarzyły. Dziękujemy wszystkim za pomoc w tym.
źródło
W tej sytuacji należy wykonać dodatkowy test, który ma na celu upewnienie się, że wielokrotne połączenia z generatorem haseł faktycznie generują różne hasła. Jeśli potrzebujesz generatora haseł bezpiecznych dla wątków, powinieneś również przetestować jednoczesne połączenia przy użyciu wielu wątków.
Zasadniczo zapewnia to prawidłowe korzystanie z losowej funkcji, a nie ponowne inicjowanie przy każdym wywołaniu.
źródło