W moich testach jednostkowych często rzucam dowolne wartości na mój kod, aby zobaczyć, co on robi. Na przykład, jeśli wiem, że foo(1, 2, 3)
ma to zwrócić 17, mógłbym napisać:
assertEqual(foo(1, 2, 3), 17)
Liczby te są czysto arbitralne i nie mają szerszego znaczenia (nie są na przykład warunkami brzegowymi, chociaż testuję je również). Trudno mi wymyślić dobre nazwiska dla tych liczb, a pisanie czegoś takiego const int TWO = 2;
jest oczywiście bezużyteczne. Czy pisanie takich testów jest w porządku, czy powinienem rozłożyć liczby na stałe?
W Czy wszystkie magiczne liczby są takie same? , dowiedzieliśmy się, że liczby magiczne są OK, jeśli znaczenie jest oczywiste z kontekstu, ale w tym przypadku liczby faktycznie nie mają żadnego znaczenia.
unit-testing
Kevin
źródło
źródło
1, 2, 3
są indeksami tablic 3D, w których wcześniej zapisałeś wartość17
, to myślę, że ten test byłby dandysowy (pod warunkiem, że masz również kilka testów negatywnych). Ale jeśli jest to wynik obliczeń, upewnij się, że każdy, kto przeczyta ten test, zrozumie, dlaczegofoo(1, 2, 3)
tak jest17
, a magiczne liczby prawdopodobnie nie osiągną tego celu.const int TWO = 2;
jest nawet gorszy niż zwykłe używanie2
. Jest zgodny z brzmieniem reguły z zamiarem naruszenia jej ducha.foo
, to nic by to nie znaczyło, a więc parametry. Ale w rzeczywistości, jestem całkiem pewny, że funkcja nie posiada tę nazwę, a parametry nie mają nazwybar1
,bar2
ibar3
. Zrób bardziej realistyczny przykład, w którym nazwy mają znaczenie, wtedy sensowniej jest omówić, czy wartości danych testowych również wymagają nazwy.Odpowiedzi:
Kiedy naprawdę masz liczby, które nie mają żadnego znaczenia?
Zwykle, gdy liczby mają jakiekolwiek znaczenie, należy przypisać je do zmiennych lokalnych metody testowej, aby kod był bardziej czytelny i zrozumiały. Nazwy zmiennych powinny przynajmniej odzwierciedlać znaczenie zmiennej, niekoniecznie jej wartość.
Przykład:
Zauważ, że pierwsza zmienna nie jest nazwana
HUNDRED_DOLLARS_ZERO_CENT
, ale wstartBalance
celu określenia jej znaczenia, ale nie to, że jej wartość jest w jakikolwiek sposób wyjątkowa.źródło
0.05f
doint
. :)const
zmiennej.calculateCompoundInterest
? Jeśli tak, to dodatkowe pisanie jest dowodem pracy, że przeczytałeś dokumentację testowanej funkcji lub przynajmniej skopiowałeś nazwy nadane przez twoje IDE. Nie jestem pewien, ile to mówi czytelnikowi o zamiarze kodu, ale jeśli przekażesz parametry w niewłaściwej kolejności, przynajmniej będą mogli powiedzieć, co było zamierzone.Jeśli używasz dowolnych liczb, aby zobaczyć, co robią, to tak naprawdę szukasz prawdopodobnie losowo wygenerowanych danych testowych lub testów opartych na właściwościach.
Na przykład Hipoteza jest fajną biblioteką Pythona do tego rodzaju testów i jest oparta na QuickCheck .
Chodzi o to, aby nie ograniczać się do własnych wartości, ale wybrać losowe, które można wykorzystać do sprawdzenia, czy funkcje spełniają ich specyfikacje. Co ważne, systemy te na ogół zapamiętują wszelkie dane wejściowe, które ulegną awarii, a następnie zapewniają, że dane wejściowe będą zawsze testowane w przyszłości.
Punkt 3 może być mylący dla niektórych osób, więc wyjaśnijmy. Nie oznacza to, że podajesz dokładną odpowiedź - jest to oczywiście niemożliwe w przypadku arbitralnych danych wejściowych. Zamiast tego twierdzisz coś o właściwości wyniku. Na przykład możesz twierdzić, że po dodaniu czegoś do listy staje się ono puste lub że samo-równoważące się drzewo wyszukiwania binarnego jest faktycznie zrównoważone (przy użyciu kryteriów określonych przez daną strukturę danych).
Ogólnie rzecz biorąc, samodzielne wybieranie dowolnych liczb jest prawdopodobnie dość złe - nie dodaje żadnej wartości i jest mylące dla każdego, kto je czyta. Automagiczne generowanie wiązki losowych danych testowych i skuteczne ich wykorzystanie jest dobre. Znalezienie hipotezy lub biblioteki podobnej do QuickCheck dla wybranego języka jest prawdopodobnie lepszym sposobem na osiągnięcie celów, pozostając zrozumiałym dla innych.
źródło
foo
jest obliczeniowe) ...? Gdybyś był w 100% pewien, że twój kod daje prawidłową odpowiedź, to po prostu umieściłbyś ten kod w programie i nie testowałeś go. Jeśli nie, musisz przetestować test i myślę, że wszyscy widzą, dokąd to zmierza.d
dniach, obliczenie wd
dniach + 1 miesiąc powinno być znaną wyższą miesięczną stopą procentową) itp.Twoja nazwa testu jednostkowego powinna zapewniać większość kontekstu. Nie z wartości stałych. Nazwa / dokumentacja testu powinna zawierać odpowiedni kontekst i wyjaśnienie wszelkich magicznych liczb obecnych w teście.
Jeśli to nie wystarczy, powinna być w stanie dostarczyć odrobinę dokumentacji (czy to poprzez nazwę zmiennej, czy dokumentację). Należy pamiętać, że sama funkcja ma parametry, które, mam nadzieję, mają sensowne nazwy. Kopiowanie ich do testu w celu nazwania argumentów jest raczej bezcelowe.
I na koniec, jeśli twoje najbardziej nieprzystosowane są na tyle skomplikowane, że jest to trudne / niepraktyczne, prawdopodobnie masz zbyt skomplikowane funkcje i możesz rozważyć, dlaczego tak jest.
Im bardziej niechętnie piszesz testy, tym gorszy będzie twój rzeczywisty kod. Jeśli czujesz potrzebę nazwania wartości testowych, aby test był przejrzysty, zdecydowanie sugeruje to, że twoja rzeczywista metoda wymaga lepszego nazewnictwa i / lub dokumentacji. Jeśli zauważysz potrzebę nazywania stałych w testach, zastanowię się, dlaczego jest to potrzebne - prawdopodobnie problemem nie jest sam test, ale implementacja
źródło
Zależy to w dużej mierze od testowanej funkcji. Znam wiele przypadków, w których poszczególne liczby same w sobie nie mają specjalnego znaczenia, ale przypadek testowy jako całość jest skrupulatnie skonstruowany i dlatego ma określone znaczenie. To właśnie należy w jakiś sposób udokumentować. Na przykład, jeśli
foo
tak naprawdę jest metoda,testForTriangle
która decyduje, czy trzy liczby mogą być prawidłowymi długościami krawędzi trójkąta, twoje testy mogą wyglądać następująco:i tak dalej. Możesz to poprawić i zamienić komentarze w parametr komunikatu,
assertEqual
który będzie wyświetlany, gdy test się nie powiedzie. Następnie możesz go jeszcze ulepszyć i przekształcić w test oparty na danych (jeśli Twoja platforma testowa to obsługuje). Niemniej jednak robisz sobie przysługę, jeśli umieścisz w kodzie notatkę, dlaczego wybrałeś te liczby i które z różnych zachowań testujesz w indywidualnym przypadku.Oczywiście w przypadku innych funkcji indywidualne wartości parametrów mogą mieć większe znaczenie, więc używanie bezsensownej nazwy funkcji, takiej jak
foo
przy pytaniu o sposób obchodzenia się ze znaczeniem parametrów, prawdopodobnie nie jest najlepszym pomysłem.źródło
Dlaczego chcemy używać nazwanych Stałych zamiast liczb?
Jeśli napiszesz kilka testów jednostkowych, każdy z zestawem 3 liczb (startBalance, odsetki, lata) - po prostu spakowałbym wartości do testu jednostkowego jako zmienne lokalne. Najmniejszy zakres, do którego należą.
Jeśli używasz języka, który pozwala na nazwane parametry, jest to oczywiście zbyteczne. Tam po prostu spakowałbym surowe wartości w wywołaniu metody. Nie wyobrażam sobie żadnego refaktoryzacji, dzięki czemu to stwierdzenie byłoby bardziej zwięzłe:
Lub użyj Framework testujący, który pozwoli ci zdefiniować przypadki testowe w jakiejś formie tablicy lub mapy:
źródło
Numery są używane do wywoływania metody, więc z pewnością powyższe założenie jest nieprawidłowe. Możesz nie dbać o to, jakie są liczby, ale to nie ma sensu. Tak, możesz wywnioskować, do jakich liczb są używane niektóre wizualizacje IDE, ale byłoby znacznie lepiej, gdybyś tylko nadał nazwy wartościom - nawet jeśli tylko pasują one do parametrów.
źródło
assertEqual "Returned value" (makeKindInt 42) (runTest "lvalue_operators")
). W tym przykładzie42
jest tylko wartością zastępczą, która jest generowana przez kod w skrypcie testowym o nazwie,lvalue_operators
a następnie sprawdzana, gdy jest zwracany przez skrypt. Nie ma żadnego znaczenia, poza tym, że ta sama wartość występuje w dwóch różnych miejscach. Jaka byłaby tutaj odpowiednia nazwa, która faktycznie nadaje jakieś użyteczne znaczenie?Jeśli chcesz przetestować czystą funkcję na jednym zestawie danych wejściowych, które nie są warunkami brzegowymi, to prawie na pewno chcesz przetestować ją na całej grupie zestawów danych wejściowych, które nie są (i są) warunkami brzegowymi. A dla mnie oznacza to, że powinna istnieć tabela wartości, za pomocą której można wywoływać funkcję, oraz pętla:
Narzędzia takie jak te sugerowane w odpowiedzi Dannnno mogą pomóc w zbudowaniu tabeli wartości do przetestowania.
bar
,baz
iblurf
powinny zostać zastąpione znaczącymi nazwami, jak omówiono w odpowiedzi Filipa .(Sporna ogólna zasada tutaj: liczby nie zawsze są „magicznymi liczbami”, które wymagają nazw; zamiast tego liczby mogą być danymi . Jeśli sensowne byłoby umieszczenie liczb w tablicy, być może w tablicy rekordów, to prawdopodobnie są to dane I odwrotnie, jeśli podejrzewasz, że możesz mieć dane w swoich rękach, rozważ umieszczenie ich w tablicy i zdobycie ich więcej).
źródło
Testy różnią się od kodu produkcyjnego i przynajmniej w testach jednostkowych napisanych w Spocku, które są krótkie i do tego stopnia, że nie mam problemu z użyciem stałych magicznych.
Jeśli test ma 5 linii długości i jest zgodny z podstawowym schematem podanym / kiedy / wtedy, wyodrębnienie takich wartości do stałych spowodowałoby tylko, że kod byłby dłuższy i trudniejszy do odczytania. Jeśli logika brzmi „Kiedy dodam użytkownika o imieniu Smith, widzę, że użytkownik Smith powrócił na listę użytkowników”, nie ma sensu wyodrębnianie „Smith” do stałej.
Ma to oczywiście zastosowanie, jeśli można łatwo dopasować wartości użyte w bloku „podanym” (setup) do tych, które można znaleźć w blokach „kiedy” i „następnie”. Jeśli Twoja konfiguracja testowa jest oddzielona (w kodzie) od miejsca, w którym dane są używane, lepiej byłoby użyć stałych. Ale ponieważ testy są najlepiej samodzielne, konfiguracja jest zwykle blisko miejsca użycia i ma zastosowanie pierwszy przypadek, co oznacza, że stałe magiczne są w tym przypadku całkiem akceptowalne.
źródło
Po pierwsze, zgódźmy się, że „test jednostkowy” jest często używany do pokrycia wszystkich testów automatycznych napisanych przez programistę i że nie ma sensu dyskutować, jak każdy test powinien być nazwany…
Pracowałem nad systemem, w którym oprogramowanie pobierało wiele danych wejściowych i opracowywałem „rozwiązanie”, które musiało spełniać pewne ograniczenia, jednocześnie optymalizując inne liczby. Nie było prawidłowych odpowiedzi, więc oprogramowanie musiało dać rozsądną odpowiedź.
W tym celu wykorzystano wiele losowych liczb, aby uzyskać punkt początkowy, a następnie „wspinacz górski”, aby poprawić wynik. To było uruchamiane wiele razy, wybierając najlepszy wynik. Generator liczb losowych może zostać zaszczepiony, aby zawsze dawał te same liczby w tej samej kolejności, dlatego jeśli test ustawi ziarno, wiemy, że wynik byłby taki sam przy każdym uruchomieniu.
Przeprowadziliśmy wiele testów, które wykonały powyższe, i sprawdziliśmy, że wyniki są takie same, co powiedziało nam, że nie zmieniliśmy tego, co ta część systemu zrobiła przez pomyłkę podczas refaktoryzacji itp. Nie powiedziała nam nic o poprawności co zrobiła ta część systemu.
Testy te były kosztowne w utrzymaniu, ponieważ każda zmiana kodu optymalizacyjnego spowodowałaby przerwanie testów, ale wykryły również błędy w znacznie większym kodzie, który wstępnie przetwarzał dane i przetwarzał wyniki.
Gdy „wyśmiewaliśmy” bazę danych, można nazwać te testy „testami jednostkowymi”, ale „jednostka” była dość duża.
Często, gdy pracujesz na systemie bez testów, robisz coś takiego jak wyżej, aby upewnić się, że refaktoryzacja nie zmienia wyniku; miejmy nadzieję, że napisano lepsze testy dla nowego kodu!
źródło
Myślę, że w tym przypadku liczby powinny być nazywane Liczbami Arbitralnymi, a nie Liczbami Magicznymi, i po prostu komentuj wiersz jako „dowolny przypadek testowy”.
Pewnie, niektóre Liczby Magiczne mogą być również dowolne, jak w przypadku unikalnych wartości „obsługi” (które oczywiście powinny zostać zastąpione nazwanymi stałymi), ale mogą być również wstępnie obliczonymi stałymi, takimi jak „prędkość nieobciążonego europejskiego wróbla w furlongach na dwa tygodnie”, gdzie wartość liczbowa jest podłączana bez komentarzy i pomocnego kontekstu.
źródło
Nie zaryzykuję, aby powiedzieć ostateczne tak / nie, ale oto kilka pytań, które powinieneś sobie zadać, decydując, czy to jest w porządku, czy nie.
Jeśli liczby nic nie znaczą, dlaczego są one na pierwszym miejscu? Czy można je zastąpić czymś innym? Czy potrafisz przeprowadzić weryfikację na podstawie wywołań metod i przepływu zamiast asercji wartości? Rozważmy coś takiego jak
verify()
metoda Mockito, która sprawdza, czy pewne wywołania metod zostały wykonane w celu wyszydzenia obiektów zamiast faktycznego potwierdzenia wartości.Jeśli numery zrobić coś znaczy, to powinny one być przypisane do zmiennych, które są nazwane odpowiednio.
Zapisanie liczby,
2
któraTWO
może być pomocna w niektórych kontekstach, a nie tyle w innych kontekstach.assertEquals(TWO, half_of(FOUR))
ma sens dla kogoś, kto czyta kod. Od razu wiadomo, co testujesz.assertEquals(numCustomersInBank(BANK_1), TWO)
, to nie ma to większego sensu. Dlaczego maBANK_1
zawierać dwie klientów? Co mamy do testowania?źródło