Często pracuję z programami numerycznymi / matematycznymi, w których dokładny wynik funkcji jest trudny do przewidzenia z góry.
Próbując zastosować TDD z tego rodzaju kodem, często uważam, że pisanie testowanego kodu jest znacznie łatwiejsze niż pisanie testów jednostkowych dla tego kodu, ponieważ jedynym sposobem na znalezienie oczekiwanego wyniku jest zastosowanie samego algorytmu (czy w moim głowa, na papierze lub przy komputerze). To wydaje się błędne, ponieważ skutecznie używam testowanego kodu do weryfikacji moich testów jednostkowych, zamiast na odwrót.
Czy znane są techniki pisania testów jednostkowych i stosowania TDD, gdy trudno jest przewidzieć wynik testowanego kodu?
(Prawdziwy) przykład kodu z trudnymi do przewidzenia wynikami:
Funkcja, weightedTasksOnTime
która biorąc pod uwagę ilość pracy wykonanej dziennie workPerDay
w zakresie (0, 24], aktualny czas initialTime
> 0 i listę zadań taskArray
; każde z czasem do ukończenia właściwości time
> 0, terminem due
i wartością ważności importance
; zwraca znormalizowana wartość z zakresu [0, 1] reprezentująca znaczenie zadań, które można wykonać przed ich due
datą, jeżeli każde zadanie zostanie wykonane w kolejności podanej przez taskArray
, począwszy od initialTime
.
Algorytm implementujący tę funkcję jest stosunkowo prosty: iteruj po zadaniach w taskArray
. Dla każdego zadania, dodać time
do initialTime
. Jeśli nowy czas < due
, dodaj importance
do akumulatora. Czas jest regulowany przez odwrotną pracęPerDay. Przed zwróceniem akumulatora podziel przez sumę ważności zadań w celu normalizacji.
function weightedTasksOnTime(workPerDay, initialTime, taskArray) {
let simulatedTime = initialTime
let accumulator = 0;
for (task in taskArray) {
simulatedTime += task.time * (24 / workPerDay)
if (simulatedTime < task.due) {
accumulator += task.importance
}
}
return accumulator / totalImportance(taskArray)
}
Wierzę, że powyższy problem można uprościć, zachowując jego rdzeń, usuwając workPerDay
i wymagając normalizacji, aby dać:
function weightedTasksOnTime(initialTime, taskArray) {
let simulatedTime = initialTime
let accumulator = 0;
for (task in taskArray) {
simulatedTime += task.time
if (simulatedTime < task.due) {
accumulator += task.importance
}
}
return accumulator
}
To pytanie dotyczy sytuacji, w których testowany kod nie jest ponowną implementacją istniejącego algorytmu. Jeśli kod jest ponowną implementacją, z natury ma on łatwe do przewidzenia wyniki, ponieważ istniejące zaufane implementacje algorytmu działają jak naturalna wyrocznia testowa.
źródło
Odpowiedzi:
Istnieją dwie rzeczy, które można przetestować w trudnym do przetestowania kodzie. Po pierwsze, zdegenerowane przypadki. Co się stanie, jeśli nie masz żadnych elementów w tablicy zadań lub tylko jeden lub dwa, ale jeden przekroczył termin realizacji itp. Wszystko, co jest prostsze niż twój prawdziwy problem, ale nadal uzasadnione do obliczenia ręcznego.
Drugi to kontrole poczytalności. Są to kontrole, które wykonujesz, gdy nie wiesz, czy odpowiedź jest prawidłowa , ale na pewno byś wiedział, czy jest zła . Są to takie rzeczy, jak czas musi iść do przodu, wartości muszą mieścić się w rozsądnym zakresie, wartości procentowe muszą się sumować do 100 itd.
Tak, nie jest to tak dobre jak pełny test, ale zdziwiłbyś się, jak często psujesz się przy sprawdzaniu czystości i zdegenerowanych przypadkach, co ujawnia problem w twoim pełnym algorytmie.
źródło
Pisałem testy oprogramowania naukowego o trudnych do przewidzenia wynikach. Często korzystaliśmy z relacji metamorficznych. Zasadniczo istnieją rzeczy, które wiesz o tym, jak powinno się zachowywać twoje oprogramowanie, nawet jeśli nie znasz dokładnych danych liczbowych.
Możliwy przykład twojej sprawy: jeśli zmniejszysz ilość pracy, którą możesz wykonać każdego dnia, łączna ilość pracy, którą możesz wykonać, pozostanie co najwyżej taka sama, ale prawdopodobnie zmniejszy się. Więc uruchom funkcję dla szeregu wartości
workPerDay
i upewnij się, że relacja jest zachowana.źródło
Inne odpowiedzi mają dobre pomysły na opracowanie testów dla przypadku krawędzi lub błędu. Dla pozostałych użycie samego algorytmu nie jest idealne (oczywiście), ale wciąż przydatne.
Wykryje, czy algorytm (lub dane, od których zależy) zmienił się
Jeśli zmiana jest wypadkiem, możesz cofnąć zatwierdzenie. Jeśli zmiana była zamierzona, musisz ponownie sprawdzić test jednostkowy.
źródło
W ten sam sposób piszesz testy jednostkowe dla dowolnego innego rodzaju kodu:
O ile kod nie zawiera losowego elementu lub nie jest deterministyczny (tj. Nie będzie generował tego samego wyniku przy tych samych danych wejściowych), można go testować jednostkowo.
Unikaj skutków ubocznych lub funkcji, na które wpływ mają siły zewnętrzne. Czyste funkcje są łatwiejsze do przetestowania.
źródło
Unless your code involves some random element
Sztuczka polega na tym, aby generator liczb losowych był wstrzykiwaną zależnością, dzięki czemu można go zastąpić generatorem liczb, który daje dokładnie pożądany wynik. Umożliwia to ponowne dokładne przetestowanie - zliczanie wygenerowanych liczb jako parametrów wejściowych.not deterministic (i.e. it won't produce the same output given the same input)
Ponieważ test jednostkowy powinien rozpoczynać się od kontrolowanej sytuacji, może być niedeterministyczny tylko wtedy, gdy zawiera element losowy - który można następnie wstrzyknąć. Nie mogę tutaj wymyślić innych możliwości.if(x == x)
, to bezcelowe porównanie. Potrzebujesz dwóch wyników ( rzeczywistych : pochodzi z kodu; oczekuje się : pochodzi z twojej wiedzy zewnętrznej), aby być niezależnymi od siebie.Aktualizacja z powodu opublikowanych komentarzy
Oryginalna odpowiedź została usunięta ze względu na zwięzłość - można ją znaleźć w historii edycji.
Po pierwsze, TL; DR, aby uniknąć długiej odpowiedzi:
Głównym problemem tutaj jest to, że nie rozdzielasz klienta od programisty (i analityka - choć tę rolę może również reprezentować programista).
Musisz rozróżnić między testowaniem kodu a testowaniem wymagań biznesowych.
Na przykład klient chce, aby działał tak [ten] . Jednak deweloper nie rozumie, a on pisze kod, który robi [to] .
Deweloper napisze zatem testy jednostkowe, które sprawdzają, czy [to] działa zgodnie z oczekiwaniami. Jeśli poprawnie opracował aplikację, jego testy jednostkowe zakończą się pomyślnie, nawet jeśli aplikacja nie zrobi tego [tego] , czego oczekiwał klient.
Jeśli chcesz przetestować oczekiwania klienta (wymagania biznesowe), musisz to zrobić w osobnym (i późniejszym) kroku.
Prosty przepływ pracy programistycznej, aby pokazać, kiedy należy uruchomić te testy:
Można się zastanawiać, o co chodzi w przeprowadzaniu dwóch osobnych testów, gdy klient i programista są jednym i tym samym. Ponieważ nie ma „przekazania” od dewelopera do klienta, testy są przeprowadzane jeden po drugim, ale nadal są to osobne kroki.
Jeśli chcesz sprawdzić, czy sam algorytm jest poprawny, nie jest to częścią zadania programisty . To troska klienta, a klient przetestuje to za pomocą aplikacji.
Jako przedsiębiorca i pracownik akademicki możesz nie zauważyć tutaj ważnego rozróżnienia, które podkreśla różne obowiązki.
źródło
Testowanie nieruchomości
Czasami funkcje matematyczne są lepiej obsługiwane przez „testowanie właściwości” niż przez tradycyjne testowanie jednostkowe oparte na przykładach. Wyobraź sobie na przykład, że piszesz testy jednostkowe dla czegoś takiego jak funkcja „mnożenia” liczb całkowitych. Chociaż sama funkcja może wydawać się bardzo prosta, jeśli jest to jedyny sposób na pomnożenie, jak ją dokładnie przetestować bez logiki w samej funkcji? Możesz użyć gigantycznych tabel z oczekiwanymi wejściami / wyjściami, ale jest to ograniczone i podatne na błędy.
W takich przypadkach można przetestować znane właściwości funkcji, zamiast szukać konkretnych oczekiwanych wyników. W przypadku mnożenia możesz wiedzieć, że pomnożenie liczby ujemnej i liczby dodatniej powinno skutkować liczbą ujemną oraz że pomnożenie dwóch liczb ujemnych powinno dać liczbę dodatnią itp. Korzystanie z losowych wartości, a następnie sprawdzenie, czy te właściwości są zachowane dla wszystkich wartości testowe to dobry sposób na przetestowanie takich funkcji. Zasadniczo musisz przetestować więcej niż jedną właściwość, ale często możesz zidentyfikować skończony zestaw właściwości, które razem sprawdzają poprawność działania funkcji, niekoniecznie znając oczekiwany wynik dla każdego przypadku.
Jednym z najlepszych wprowadzeń do testowania własności, które widziałem, jest ten w F #. Mam nadzieję, że składnia nie jest przeszkodą w zrozumieniu wyjaśnienia techniki.
źródło
Kuszące jest napisanie kodu, a następnie sprawdzenie, czy wynik „wygląda dobrze”, ale, jak słusznie intuicyjnie, nie jest to dobry pomysł.
Gdy algorytm jest trudny, możesz zrobić wiele rzeczy, aby ułatwić ręczne obliczenie wyniku.
Użyj Excela. Skonfiguruj arkusz kalkulacyjny, który wykonuje niektóre lub wszystkie obliczenia za Ciebie. Uprość to na tyle, aby można było zobaczyć kroki.
Podziel metodę na mniejsze, testowalne, każda z własnymi testami. Gdy masz pewność, że mniejsze części działają, użyj ich do ręcznej pracy przez następny krok.
Użyj właściwości agregujących do sprawdzenia poprawności. Załóżmy na przykład, że masz kalkulator prawdopodobieństwa; możesz nie wiedzieć, jakie powinny być poszczególne wyniki, ale wiesz, że wszystkie muszą się sumować do 100%.
Brutalna siła. Napisz program, który generuje wszystkie możliwe wyniki, i sprawdź, czy żaden z nich nie jest lepszy od generowanego przez algorytm.
źródło
TL; DR
Przejdź do sekcji „testy porównawcze”, aby uzyskać porady, których nie ma w innych odpowiedziach.
Początki
Zacznij od przetestowania przypadków, które powinny zostać odrzucone przez algorytm (
workPerDay
na przykład zero lub ujemne ) oraz spraw, które są trywialne (np. Pustatasks
tablica).Następnie najpierw przetestuj najprostsze przypadki. W przypadku
tasks
danych wejściowych musimy przetestować różne długości; powinno wystarczyć przetestowanie 0, 1 i 2 elementów (2 należy do kategorii „wielu” dla tego testu).Jeśli znajdziesz dane, które można obliczyć mentalnie, to dobry początek. Techniką, której czasem używam, jest rozpoczęcie od pożądanego wyniku i powrót (w specyfikacji) do danych wejściowych, które powinny dać ten wynik.
Testy porównawcze
Czasami stosunek wyniku do wejścia nie jest oczywisty, ale istnieje przewidywalna zależność między różnymi wyjściami, gdy jedno wejście jest zmieniane. Jeśli dobrze zrozumiałem przykład, dodanie zadania (bez zmiany innych danych wejściowych) nigdy nie zwiększy proporcji pracy wykonanej na czas, dzięki czemu możemy utworzyć test, który wywołuje funkcję dwukrotnie - raz z dodatkowym zadaniem i raz bez niego - i zapewnia nierówność między tymi dwoma wynikami.
Awarie
Czasami musiałem uciekać się do długiego komentarza pokazującego ręcznie obliczony wynik w krokach odpowiadających specyfikacji (taki komentarz jest zwykle dłuższy niż przypadek testowy). Najgorszy przypadek to konieczność zachowania zgodności z wcześniejszą implementacją w innym języku lub w innym środowisku. Czasami wystarczy po prostu oznaczyć dane testowe czymś takim jak
/* derived from v2.6 implementation on ARM system */
. To nie jest bardzo satysfakcjonujące, ale może być do zaakceptowania jako test wierności podczas przenoszenia lub jako krótkotrwała kula.Przypomnienia
Najważniejszą cechą testu jest jego czytelność - jeśli wejścia i wyjścia są nieprzezroczyste dla czytnika, wówczas test ma bardzo niską wartość, ale jeśli czytelnikowi pomaga się zrozumieć relacje między nimi, test służy dwóm celom.
Nie zapomnij użyć odpowiedniego „w przybliżeniu równego” dla niedokładnych wyników (np. Zmiennoprzecinkowe).
Unikaj nadmiernego testowania - dodaj test tylko wtedy, gdy obejmuje coś (np. Wartość graniczną), którego nie osiągają inne testy.
źródło
Nie ma nic specjalnego w tego rodzaju trudnych do przetestowania funkcjach. To samo dotyczy kodu korzystającego z zewnętrznych interfejsów (powiedzmy interfejsu API REST aplikacji innej firmy, która nie jest pod twoją kontrolą i na pewno nie będzie testowana przez pakiet testowy; lub przy użyciu biblioteki innej firmy, jeśli nie masz pewności dokładny bajtowy format zwracanych wartości).
Jest to całkiem poprawne podejście, aby po prostu uruchomić algorytm dla pewnych rozsądnych danych wejściowych, zobaczyć, co robi, upewnić się, że wynik jest poprawny, i zamknąć dane wejściowe i wynik jako przypadek testowy. Możesz to zrobić dla kilku przypadków, a tym samym uzyskać kilka próbek. Spróbuj ustawić parametry wejściowe tak różne, jak to możliwe. W przypadku zewnętrznego wywołania API wykonujesz kilka połączeń z prawdziwym systemem, śledzisz je za pomocą jakiegoś narzędzia, a następnie wyśmiewasz je do testów jednostkowych, aby zobaczyć, jak zareaguje Twój program - co jest równoznaczne z wybraniem kilku uruchamia kod planowania zadania, weryfikując je ręcznie, a następnie zapisując wynik w testach.
Następnie, oczywiście, przynieś przypadkowe przypadki, takie jak (w twoim przykładzie) pusta lista zadań; rzeczy takie jak te.
Zestaw testów może nie być tak świetny, jak metoda, w której można łatwo przewidzieć wyniki; ale wciąż o 100% lepszy niż brak zestawu testów (lub tylko test dymu).
Jeśli twoim problemem jest to, że trudno ci zdecydować, czy wynik jest poprawny, to jest to zupełnie inny problem. Załóżmy na przykład, że masz metodę, która wykrywa, czy dowolna duża liczba jest liczbą pierwszą. Trudno rzucić na nią dowolną liczbą losową, a następnie po prostu „spojrzeć”, jeśli wynik jest prawidłowy (zakładając, że nie można zdecydować o pierwszorzędności w głowie lub na kartce papieru). W tym przypadku naprawdę niewiele można zrobić - trzeba albo poznać znane wyniki (np. Niektóre duże liczby pierwsze), albo wdrożyć funkcjonalność za pomocą innego algorytmu (może nawet innego zespołu - NASA wydaje się lubić that) i mam nadzieję, że jeśli którakolwiek implementacja jest błędna, przynajmniej błąd nie prowadzi do takich samych błędnych wyników.
Jeśli jest to zwykły przypadek, musisz dobrze porozmawiać z inżynierami wymagań. Jeśli nie są w stanie sformułować twoich wymagań w sposób łatwy (lub w ogóle możliwy) do sprawdzenia, to kiedy wiesz, czy jesteś skończony?
źródło
Inne odpowiedzi są dobre, więc postaram się trafić w niektóre punkty, które dotychczas wspólnie przegapili.
Napisałem (i dokładnie przetestowałem) oprogramowanie do przetwarzania obrazu za pomocą radaru z syntetyczną aperturą (SAR). Ma charakter naukowy / liczbowy (wymaga dużo geometrii, fizyki i matematyki).
Kilka wskazówek (dotyczących ogólnych badań naukowych / numerycznych):
1) Użyj odwrotności. Co się
fft
z[1,2,3,4,5]
? Brak pomysłu. Co jestifft(fft([1,2,3,4,5]))
? Powinien być[1,2,3,4,5]
(lub blisko niego, mogą pojawić się błędy zmiennoprzecinkowe). To samo dotyczy przypadku 2D.2) Użyj znanych twierdzeń. Jeśli napiszesz funkcję wyznacznika, może być trudno powiedzieć, czym jest wyznacznik losowej macierzy 100 x 100. Ale wiesz, że wyznacznikiem macierzy tożsamości jest 1, nawet jeśli jest 100 x 100. Wiesz również, że funkcja powinna zwracać 0 na nieodwracalnej macierzy (jak 100 x 100 pełna wszystkich zer).
3) Używaj szorstkich stwierdzeń zamiast dokładnych stwierdzeń. Napisałem kod dla wspomnianego przetwarzania SAR, który zarejestrowałby dwa obrazy, generując punkty wiążące, które tworzą mapowanie między obrazami, a następnie wykonując wypaczenie między nimi, aby je dopasować. Może zarejestrować się na poziomie subpikseli. A priori trudno powiedzieć coś o tym, jak mogłaby wyglądać rejestracja dwóch zdjęć. Jak możesz to przetestować? Rzeczy jak:
ponieważ możesz zarejestrować się tylko na nakładających się częściach, zarejestrowany obraz musi być mniejszy lub równy twojemu najmniejszemu obrazowi, a także:
ponieważ zarejestrowany sam obraz powinien być ZAMKNIĘTY dla siebie, ale możesz napotkać nieco więcej niż błędy zmiennoprzecinkowe z powodu zastosowanego algorytmu, więc po prostu sprawdź, czy każdy piksel mieści się w zakresie +/- 5% zakresu, jaki piksele mogą przyjąć (0-255 to skala szarości, powszechna w przetwarzaniu obrazu). Wynik powinien mieć co najmniej taki sam rozmiar jak wejście.
Możesz nawet po prostu przetestować dym (tzn. Zadzwoń i upewnij się, że się nie zawiesi). Ogólnie rzecz biorąc, ta technika jest lepsza w przypadku większych testów, w których wyniku końcowego nie można (łatwo) obliczyć a priori przed uruchomieniem testu.
4) Użyj LUB ZAPISZ losowy numer początkowy dla swojego RNG.
Działa nie muszą być powtarzalne. Fałszywe jest jednak, że jedynym sposobem na uzyskanie powtarzalnego przebiegu jest dostarczenie określonego ziarna do generatora liczb losowych. Czasami testowanie losowości jest cenne. Widziałem / słyszałem o błędy w kodzie naukowych, które pojawić się w zdegenerowanych przypadkach, które zostały losowo generowanych (w skomplikowanych algorytmów może być trudno zobaczyć, co przypadek zdegenerowany nawet jest). Zamiast zawsze wywoływać funkcję z tym samym ziarnem, wygeneruj losowe ziarno, a następnie użyj tego ziarna i zapisz jego wartość. W ten sposób każde uruchomienie ma inne losowe ziarno, ale jeśli wystąpi awaria, możesz ponownie uruchomić wynik, używając ziarna, które zalogowałeś do debugowania. Właściwie użyłem tego w praktyce i to zmiażdżyło błąd, więc pomyślałem, że wspomnę o tym. To prawda, że stało się to tylko raz i jestem pewien, że nie zawsze warto to robić, więc używaj tej techniki z rozwagą. Losowe z tym samym ziarnem jest jednak zawsze bezpieczne. Wada (w przeciwieństwie do ciągłego korzystania z tego samego materiału siewnego): Musisz zalogować swoje testy. Zaleta: poprawność i nukanie błędów.
Twoja szczególna sprawa
1) Sprawdź, czy pusty
taskArray
zwraca 0 (znane twierdzenie).2) Generowanie losowych danych wejściowych tak, że
task.time > 0
,task.due > 0
, atask.importance > 0
dla wszystkichtask
s, i twierdzą, wynik jest większy niż0
(Gorsza Twierdzę wejściowym losowe) . Nie musisz zwariować i wygenerować losowych nasion, twój algorytm po prostu nie jest wystarczająco skomplikowany, aby to uzasadnić. Jest około 0 szans, że się opłaci: po prostu uprość test.3) Sprawdź, czy
task.importance == 0
dla wszystkichtask
s wynik jest0
(znany)4) Dotknęły tego inne odpowiedzi, ale może to mieć znaczenie dla konkretnego przypadku: jeśli tworzysz interfejs API do użytku przez użytkowników spoza zespołu, musisz przetestować zdegenerowane przypadki. Na przykład, jeśli
workPerDay == 0
zgłaszasz piękny błąd, który informuje użytkownika, że dane wejściowe są nieprawidłowe. Jeśli nie tworzysz interfejsu API i jest on przeznaczony tylko dla Ciebie i Twojego zespołu, prawdopodobnie możesz pominąć ten krok i po prostu odmówić połączenia z degeneracją.HTH.
źródło
Włącz testowanie asercji do pakietu testów jednostkowych w celu testowania algorytmu na podstawie właściwości. Oprócz pisania testów jednostkowych, które sprawdzają określone dane wyjściowe, pisz testy zaprojektowane z myślą o niepowodzeniu przez wyzwalanie błędów asercji w kodzie głównym.
Wiele algorytmów opiera się na dowodach poprawności na zachowaniu określonych właściwości na wszystkich etapach algorytmu. Jeśli możesz rozsądnie sprawdzić te właściwości, patrząc na wynik funkcji, wystarczy testowanie jednostkowe, aby przetestować swoje właściwości. W przeciwnym razie testy oparte na asercjach pozwalają przetestować, czy implementacja zachowuje właściwość za każdym razem, gdy algorytm ją przyjmuje.
Testy oparte na asercji ujawnią wady algorytmu, błędy w kodowaniu i błędy implementacji z powodu takich problemów, jak niestabilność numeryczna. Wiele języków ma mechanizmy usuwania asercji w czasie kompilacji lub przed interpretacją kodu, aby podczas uruchamiania w trybie produkcyjnym asercje nie powodowały pogorszenia wydajności. Jeśli Twój kod przejdzie testy jednostkowe, ale nie powiedzie się w prawdziwej sprawie, możesz włączyć asercje z powrotem jako narzędzie do debugowania.
źródło
Niektóre inne odpowiedzi tutaj są bardzo dobre:
... dodałbym kilka innych taktyk:
Dekompozycja pozwala upewnić się, że składniki algorytmu wykonują to, czego się od nich oczekuje. A „dobry” rozkład pozwala również upewnić się, że są odpowiednio sklejone. Wielki rozkład uogólnia i upraszcza algorytm do tego stopnia, że można przewidzieć rezultaty (uproszczonego, rodzajowego algorytmu (ów)) na rękę tyle dobrze, żeby napisać dokładne testy.
Jeśli nie możesz rozłożyć się w takim stopniu, udowodnij, że algorytm poza kodem jest w jakikolwiek sposób wystarczający, aby zadowolić Ciebie i Twoich rówieśników, interesariuszy i klientów. A potem wystarczy rozłożyć tyle, by udowodnić, że twoja implementacja pasuje do projektu.
źródło
Może to wydawać się idealistyczną odpowiedzią, ale pomaga zidentyfikować różne rodzaje testów.
Jeśli dla implementacji ważne są ścisłe odpowiedzi, wówczas w wymaganiach opisujących algorytm należy podać przykłady i oczekiwane odpowiedzi. Te wymagania powinny zostać przejrzane grupowo, a jeśli nie uzyskasz takich samych wyników, powód musi zostać zidentyfikowany.
Nawet jeśli grasz zarówno jako analityk, jak i implementator, powinieneś stworzyć wymagania i sprawdzić je na długo przed napisaniem testów jednostkowych, więc w takim przypadku będziesz znać oczekiwane wyniki i odpowiednio napisać testy.
Z drugiej strony, jeśli jest to implementowana część, która albo nie jest częścią logiki biznesowej, albo obsługuje odpowiedź logiki biznesowej, to dobrze jest uruchomić test, aby zobaczyć, jakie są wyniki, a następnie zmodyfikować test, aby się spodziewać te wyniki. Wyniki końcowe są już sprawdzane pod kątem wymagań, więc jeśli są poprawne, cały kod zasilający te wyniki końcowe musi być poprawny numerycznie, a w tym momencie testy jednostkowe służą raczej do wykrywania przypadków awarii krawędzi i przyszłych zmian refaktoryzacji niż do udowodnienia, że dane algorytm daje prawidłowe wyniki.
źródło
Myślę, że czasami jest całkowicie do przyjęcia proces:
Jest to rozsądne podejście w każdej sytuacji, w której ręczne sprawdzenie poprawności odpowiedzi jest łatwiejsze niż ręczne obliczenie odpowiedzi na podstawie pierwszych zasad.
Znam ludzi, którzy piszą oprogramowanie do renderowania wydrukowanych stron i mają testy, które sprawdzają, czy na wydrukowanej stronie ustawione są dokładnie odpowiednie piksele. Jedynym rozsądnym sposobem na to jest napisanie kodu do renderowania strony, sprawdzenie wzrokowo, czy wygląda dobrze, a następnie przechwycenie wyniku jako testu regresji dla przyszłych wydań.
Tylko dlatego, że czytasz w książce, że określona metodologia zachęca najpierw do napisania przypadków testowych, nie oznacza, że zawsze musisz to robić w ten sposób. Zasady są do złamania.
źródło
Inne odpowiedzi odpowiedzi już zawierają techniki, jak wygląda test, gdy konkretnego wyniku nie można ustalić poza testowaną funkcją.
To, co robię dodatkowo, czego nie zauważyłem w innych odpowiedziach, to automatyczne generowanie testów w jakiś sposób:
Na przykład, jeśli funkcja przyjmuje trzy parametry o dozwolonym zakresie wejściowym [-1,1], przetestuj wszystkie kombinacje każdego parametru, {-2, -1.01, -1, -0,99, -0,5, -0,01, 0,0,01 , 0,5,0,99,1,1,01,2, niektóre losowe w (-1,1)}
W skrócie: Czasami słaba jakość może być subsydiowana ilościowo.
źródło