Staram się, aby mój kod był bardziej niezawodny i czytałem o testowaniu jednostkowym, ale bardzo trudno mi znaleźć rzeczywiste użyteczne zastosowanie. Na przykład przykład z Wikipedii :
public class TestAdder {
public void testSum() {
Adder adder = new AdderImpl();
assert(adder.add(1, 1) == 2);
assert(adder.add(1, 2) == 3);
assert(adder.add(2, 2) == 4);
assert(adder.add(0, 0) == 0);
assert(adder.add(-1, -2) == -3);
assert(adder.add(-1, 1) == 0);
assert(adder.add(1234, 988) == 2222);
}
}
Uważam, że ten test jest całkowicie bezużyteczny, ponieważ musisz ręcznie obliczyć pożądany wynik i przetestować go, wydaje mi się, że lepszy byłby tutaj test jednostkowy
assert(adder.add(a, b) == (a+b));
ale to tylko kodowanie samej funkcji w teście. Czy ktoś może podać mi przykład, w którym testowanie jednostkowe jest rzeczywiście przydatne? Do twojej wiadomości Obecnie koduję głównie funkcje „proceduralne”, które pobierają ~ 10 logicznych i kilka liczb całkowitych i dają mi wynik int oparty na tym, wydaje mi się, że jedynym testem jednostkowym, który mógłbym zrobić, byłoby po prostu ponowne kodowanie algorytmu w test. edytuj: Powinienem był również sprecyzować, że dzieje się to podczas przenoszenia (prawdopodobnie źle zaprojektowanego) kodu ruby (którego nie stworzyłem)
źródło
How does unit testing work?
Nikt tak naprawdę nie wie :)Odpowiedzi:
Testy jednostkowe, jeśli testujesz wystarczająco małe jednostki, zawsze zapewniają oślepiająco oczywiste.
Powodem,
add(x, y)
dla którego pojawia się nawet wzmianka o teście jednostkowym, jest to, że jakiś czas później ktoś wejdzieadd
i umieści specjalny kod obsługi logiki podatkowej, nie wiedząc, że dodatek jest wszędzie używany.Testy jednostkowe w dużej mierze dotyczą zasady asocjacji: jeśli A robi B, a B robi C, to A robi C. „A robi C” jest testem wyższego poziomu. Rozważmy na przykład następujący, całkowicie uzasadniony kod biznesowy:
Na pierwszy rzut oka wygląda to na niesamowitą metodę testów jednostkowych, ponieważ ma bardzo wyraźny cel. Jednak robi około 5 różnych rzeczy. Każda rzecz ma prawidłowy i nieprawidłowy przypadek i spowoduje ogromną permutację testów jednostkowych. Idealnie jest to podzielone dalej:
Teraz sprowadzamy się do jednostek. Jeden test jednostkowy nie przejmuje się tym, co
_userRepo
uważa za prawidłowe zachowanieFetchValidUser
, tylko że się nazywa. Możesz użyć innego testu, aby upewnić się, co dokładnie stanowi prawidłowy użytkownik. Podobnie dlaCheckUserForRole
... oddzieliłeś swój test od wiedzy, jak wygląda struktura roli. Oddzieliłeś także cały program od ścisłego przywiązaniaSession
. Wyobrażam sobie, że wszystkie brakujące elementy tutaj wyglądałyby tak:Dokonując refaktoryzacji, osiągnąłeś kilka rzeczy na raz. Program jest znacznie bardziej pomocny w odrywaniu podstawowych struktur (możesz porzucić warstwę bazy danych dla NoSQL) lub płynnym dodawaniu blokowania, gdy uznasz, że
Session
nie jest bezpieczne dla wątków. Dałeś też teraz bardzo proste testy dla tych trzech zależności.Mam nadzieję że to pomoże :)
źródło
Jestem prawie pewien, że każda z twoich funkcji proceduralnych jest deterministyczna, więc zwraca określony wynik int dla każdego zestawu wartości wejściowych. Idealnie byłoby, gdybyś miał funkcjonalną specyfikację, z której możesz dowiedzieć się, jaki wynik powinieneś otrzymać dla niektórych zestawów wartości wejściowych. W przeciwnym razie można uruchomić kod ruby (który, jak się zakłada, działa poprawnie) dla niektórych zestawów wartości wejściowych i zapisać wyniki. Następnie musisz ZWIĘKSZYĆ KOD wyniki w teście. Test ma być dowodem na to, że Twój kod rzeczywiście generuje wyniki, o których wiadomo, że są poprawne .
źródło
Ponieważ wydaje się, że nikt inny nie podał faktycznego przykładu:
Mówisz:
Ale chodzi o to, że znacznie rzadziej popełniasz błąd (lub przynajmniej bardziej prawdopodobne, że zauważysz swoje błędy) podczas wykonywania obliczeń ręcznych niż podczas kodowania.
Jak prawdopodobne jest, że popełnisz błąd w kodzie konwersji dziesiętnej na rzymską? Całkiem prawdopodobne. Jak prawdopodobne jest, że popełnisz błąd przy ręcznej konwersji cyfr dziesiętnych na rzymskie? Niezbyt prawdopodobne. Dlatego testujemy na podstawie ręcznych obliczeń.
Jak prawdopodobne jest, że pomylisz się, gdy zastosujesz funkcję pierwiastka kwadratowego? Całkiem prawdopodobne. Jak prawdopodobne jest, że popełnisz błąd przy ręcznym obliczaniu pierwiastka kwadratowego? Prawdopodobnie bardziej prawdopodobne. Ale dzięki sqrt możesz użyć kalkulatora, aby uzyskać odpowiedzi.
Spekuluję więc o tym, co się tutaj dzieje. Twoje funkcje są dość skomplikowane, więc trudno jest ustalić na podstawie danych wejściowych, jakie powinny być dane wyjściowe. Aby to zrobić, musisz ręcznie uruchomić (w swojej głowie) funkcję, aby dowiedzieć się, jaki jest wynik. Zrozumiałe, że wydaje się to trochę bezużyteczne i podatne na błędy.
Kluczem jest to, że chcesz znaleźć prawidłowe dane wyjściowe. Ale musisz przetestować te wyniki pod kątem czegoś, o czym wiadomo, że jest poprawne. Nie warto pisać własnego algorytmu, aby to obliczyć, ponieważ może to być bardzo niepoprawne. W takim przypadku zbyt trudno jest ręcznie obliczyć wartości.
Wrócę do kodu ruby i wykonam te oryginalne funkcje z różnymi parametrami. Wziąłbym wyniki kodu ruby i umieściłem je w teście jednostkowym. W ten sposób nie musisz wykonywać ręcznych obliczeń. Ale testujesz na podstawie oryginalnego kodu. To powinno pomóc utrzymać te same wyniki, ale jeśli w oryginale są błędy, to ci nie pomoże. Zasadniczo możesz traktować oryginalny kod jak kalkulator w przykładzie sqrt.
Jeśli pokazałeś faktyczny kod, który przenosisz, możemy udzielić bardziej szczegółowych informacji zwrotnych na temat sposobu rozwiązania problemu.
źródło
Masz prawie rację dla takiej prostej klasy.
Wypróbuj bardziej skomplikowany kalkulator. Jak kalkulator kręgli.
Wartość testów jednostkowych jest łatwiej dostrzegalna, gdy masz bardziej złożone reguły „biznesowe” z różnymi scenariuszami do przetestowania.
Nie twierdzę, że nie powinieneś testować przebiegu kalkulatora młyna (czy twoje konto kalkulatora ma problemy z wartościami takimi jak 1/3, których nie można przedstawić? Co to robi z dzieleniem przez zero?), Ale zobaczysz zwiększaj wartość, jeśli testujesz coś z większą liczbą oddziałów, aby uzyskać zasięg.
źródło
Pomimo religijnej gorliwości około 100% pokrycia kodu, powiem, że nie każda metoda powinna być testowana jednostkowo. Tylko funkcjonalność zawierająca sensowną logikę biznesową. Funkcja, która po prostu dodaje liczbę, nie ma sensu testować.
Tam jest twój prawdziwy problem. Jeśli testowanie jednostkowe wydaje się nienaturalnie trudne lub bezcelowe, oznacza to prawdopodobnie wadę projektową. Gdyby był bardziej zorientowany obiektowo, twoje sygnatury metod nie byłyby tak masywne i byłoby mniej możliwych danych wejściowych do przetestowania.
Nie muszę wchodzić do mojego OO, jest lepszy od programowania proceduralnego ...
źródło
Z mojego punktu widzenia testy jednostkowe są nawet przydatne w twojej małej klasie adderów: nie myśl o „przekodowaniu” algorytmu i myśl o nim jak o czarnej skrzynce z jedyną wiedzą, jaką posiadasz, o funkcjonalnym zachowaniu (jeśli znasz dzięki szybkiemu mnożeniu znasz kilka szybszych, ale bardziej złożonych prób niż użycie „a * b”) i publicznego interfejsu. Następnie powinieneś zadać sobie pytanie „Co do diabła może pójść nie tak?” ...
W większości przypadków dzieje się to na granicy (widzę, że testujesz już dodawanie tych wzorów ++, -, + -, 00 - czas na ich uzupełnienie przez - +, 0+, 0-, +0, -0). Pomyśl o tym, co dzieje się w MAX_INT i MIN_INT podczas dodawania lub odejmowania (dodawania negatywów;)). Lub postaraj się, aby twoje testy wyglądały dokładnie tak, jak dzieje się w okolicach zera.
Podsumowując, sekret jest bardzo prosty (być może także dla bardziej złożonych;)) dla prostych klas: pomyśl o umowach swojej klasy (zobacz projekt po umowie), a następnie przetestuj je. Im lepiej poznasz swoje inv, pre i post jako „uzupełniające” twoje testy.
Wskazówka dla klas testowych: spróbuj zapisać tylko jedną aser w metodzie. Nadaj metodom dobre nazwy (np. „TestAddingToMaxInt”, „testAddingTwoNegatives”), aby uzyskać najlepszą informację zwrotną w przypadku niepowodzenia testu po zmianie kodu.
źródło
Zamiast testować ręcznie obliczoną wartość zwracaną lub powielając logikę w teście w celu obliczenia oczekiwanej wartości zwracanej, przetestuj wartość zwracaną dla oczekiwanej właściwości.
Na przykład, jeśli chcesz przetestować metodę odwracającą macierz, nie chcesz ręcznie odwracać wartości wejściowej, powinieneś pomnożyć wartość zwracaną przez dane wejściowe i sprawdzić, czy otrzymujesz macierz tożsamości.
Aby zastosować to podejście do metody, należy wziąć pod uwagę jej cel i semantykę, aby określić, jakie właściwości będzie mieć wartość zwracana w stosunku do danych wejściowych.
źródło
Testy jednostkowe są narzędziem produktywności. Otrzymujesz żądanie zmiany, zaimplementuj je, a następnie uruchom kod za pomocą gambit testu jednostkowego. To automatyczne testowanie oszczędza czas.
I feel that this test is totally useless, because you are required to manually compute the wanted result and test it, I feel like a better unit test here would be
Punkt sporny. Test w tym przykładzie pokazuje, jak utworzyć instancję klasy i przeprowadzić ją przez serię testów. Koncentrując się na szczegółach pojedynczego wdrożenia, brakuje lasu dla drzew.
Can someone provide me with an example where unit testing is actually useful?
Masz podmiot pracownika. Podmiot zawiera nazwę i adres. Klient decyduje się na dodanie pola ReportsTo.
To podstawowy test BL do pracy z pracownikiem. Kod przejdzie / zakończy się właśnie wprowadzoną zmianą schematu. Pamiętaj, że twierdzenia nie są jedyną rzeczą, którą robi test. Uruchomienie kodu zapewnia również, że nie zostaną wprowadzone żadne wyjątki.
Z czasem posiadanie testów ułatwia wprowadzanie zmian w ogóle. Kod jest automatycznie testowany pod kątem wyjątków i podjętych przez ciebie asercji. Pozwala to uniknąć dużych kosztów ogólnych spowodowanych ręcznym testowaniem przez grupę kontroli jakości. Chociaż interfejs użytkownika jest nadal dość trudny do zautomatyzowania, pozostałe warstwy są na ogół bardzo łatwe, zakładając, że prawidłowo używasz modyfikatorów dostępu.
I feel like the only unit testing I could do would be to simply re-code the algorithm in the test.
Nawet logika proceduralna jest łatwo zamknięta w funkcji. Obuduj, utwórz instancję i przekaż int / primitive do przetestowania (lub próbnego obiektu). Nie kopiuj wklej kodu do testu jednostkowego. To pokonuje SUCHO. Powoduje to także całkowite pokonanie testu, ponieważ nie testujesz kodu, ale jego kopia. Jeśli kod, który powinien zostać przetestowany, zmieni się, test nadal się powiedzie!
źródło
Biorąc twój przykład (z odrobiną refaktoryzacji),
nie pomaga:
math.add
zachowuje wewnętrznie,To prawie tak, jak powiedzenie:
math.add
może zawierać setki LOC; patrz poniżej).Oznacza to również, że nie musisz dodawać testów takich jak:
Nie pomagają ani, przynajmniej, gdy raz uczynisz pierwsze twierdzenie, drugie nie przyniesie nic pożytecznego.
Zamiast tego:
Jest to zrozumiałe i cholernie pomocne zarówno dla ciebie, jak i dla osoby, która zachowa kod źródłowy później. Wyobraź sobie, że ta osoba wprowadza niewielką modyfikację, aby
math.add
uprościć kod i zoptymalizować wydajność, i widzi wynik testu w następujący sposób:osoba ta natychmiast zrozumie, że nowo zmodyfikowana metoda zależy od kolejności argumentów: jeśli pierwszy argument jest liczbą całkowitą, a drugi długą liczbą, wynik będzie liczbą całkowitą, podczas gdy oczekiwano długiej liczby.
W ten sam sposób uzyskanie rzeczywistej wartości
4.141592
pierwszego stwierdzenia jest oczywiste: wiesz, że oczekuje się, że metoda poradzi sobie z dużą precyzją , ale tak naprawdę się nie powiedzie.Z tego samego powodu w niektórych językach mogą mieć sens dwa następujące stwierdzenia:
A co z:
Nie trzeba tłumaczyć: chcesz, aby twoja metoda była w stanie poprawnie radzić sobie z nieskończonością. Wyjście poza nieskończoność lub zgłoszenie wyjątku nie jest oczekiwanym zachowaniem.
A może, w zależności od języka, będzie to miało większy sens?
źródło
W przypadku bardzo prostej funkcji, takiej jak dodawanie, testowanie można uznać za niepotrzebne, ale ponieważ funkcje stają się coraz bardziej złożone, staje się coraz bardziej oczywiste, dlaczego testowanie jest konieczne.
Pomyśl o tym, co robisz podczas programowania (bez testów jednostkowych). Zwykle piszesz kod, uruchamiasz go, widzisz, że działa i przechodzisz do następnej rzeczy, prawda? Gdy piszesz więcej kodu, szczególnie w bardzo dużym systemie / GUI / witrynie internetowej, okazuje się, że musisz robić coraz więcej „uruchamiania i sprawdzania, czy to działa”. Musisz tego spróbować i spróbować. Następnie dokonujesz kilku zmian i musisz spróbować tych samych rzeczy od nowa. Staje się bardzo oczywiste, że możesz zaoszczędzić czas, pisząc testy jednostkowe, które zautomatyzują całą część „uruchamiania i sprawdzania, czy to działa”.
Gdy twoje projekty stają się coraz większe, liczba rzeczy, które musisz „uruchomić i zobaczyć, czy to działa”, staje się nierealna. Tak więc kończysz tylko uruchamianie i wypróbowywanie kilku głównych komponentów GUI / projektu, a następnie masz nadzieję, że wszystko inne jest w porządku. To jest przepis na katastrofę. Oczywiście, jako człowiek, nie możesz wielokrotnie testować każdej możliwej sytuacji, z której mogą skorzystać Twoi klienci, jeśli GUI jest używany przez dosłownie setki osób. Jeśli posiadasz testy jednostkowe, możesz po prostu uruchomić test przed wysłaniem stabilnej wersji lub nawet przed przekazaniem do centralnego repozytorium (jeśli twoje miejsce pracy korzysta z niego). A jeśli później pojawią się jakieś błędy, możesz po prostu dodać test jednostkowy, aby sprawdzić go w przyszłości.
źródło
Jedną z zalet pisania testów jednostkowych jest to, że pomaga pisać bardziej niezawodny kod, zmuszając do myślenia o przypadkowych przypadkach. Co powiesz na testowanie niektórych przypadków krawędzi, takich jak przepełnienie liczb całkowitych, obcięcie dziesiętne lub obsługa wartości zerowych dla parametrów?
źródło
Być może zakładasz, że add () został zaimplementowany z instrukcją ADD. Jeśli jakiś młodszy programista lub inżynier sprzętu ponownie zaimplementował funkcję add () za pomocą ANDS / ORS / XORS, bitów odwraca i przesuwa, możesz chcieć przetestować to urządzenie pod kątem instrukcji ADD.
Zasadniczo, jeśli zastąpisz wnętrzności add () lub testowanej jednostki losową generacją liczb lub generatora wyjściowego, skąd wiesz, że coś się zepsuło? Zakoduj tę wiedzę w testach jednostkowych. Jeśli nikt nie jest w stanie stwierdzić, czy jest zepsuty, po prostu wpisz kod rand () i idź do domu, praca jest skończona.
źródło
Mógłbym przeoczyć ją wśród wszystkich odpowiedzi, ale dla mnie głównym motorem testów jednostkowych jest nie tyle udowodnienie dzisiaj poprawności metody, ale to, że udowadnia ona ciągłą poprawność tej metody, gdy [kiedykolwiek] ją zmienisz .
Weź prostą funkcję, np. Zwracanie liczby elementów w niektórych kolekcjach. Dzisiaj, gdy twoja lista oparta jest na jednej wewnętrznej strukturze danych, którą dobrze znasz, możesz pomyśleć, że ta metoda jest tak boleśnie oczywista, że nie potrzebujesz do niej testu. Następnie, za kilka miesięcy lub lat, ty (lub ktoś inny ) zdecydujesz się zastąpić wewnętrzną strukturę listy. Nadal musisz wiedzieć, że getCount () zwraca poprawną wartość.
Właśnie tam twoje testy jednostkowe naprawdę się sprawdzają.
Możesz zmienić wewnętrzną implementację swojego kodu, ale dla wszystkich odbiorców tego kodu wynik pozostaje taki sam.
źródło