Czy istnieje potrzeba przechowywania testów prostych (samodzielnych) funkcji?

36

Rozważ to:

public function polynominal($a, $b, $c, $d)
{
    return  $a * pow($x, 3) + $b * pow($x, 2) + $c * $x + $d;
}

Załóżmy, że piszesz różne testy dla powyższej funkcji i udowodnisz sobie i innym, że „to działa”.

Dlaczego więc nie usunąć tych testów i żyć długo i szczęśliwie? Chodzi mi o to, że niektóre funkcje nie muszą być ciągle testowane po udowodnieniu, że działają. Szukam kontrapunktów, które stwierdzają, tak, te funkcje wciąż muszą zostać przetestowane, ponieważ: ... Albo że tak, nie trzeba ich testować ...

Dennis
źródło
32
Czy nie jest prawdą, że dla każdej funkcji, jeśli nie zmienisz kodu, jeśli zadziałał wczoraj, zadziała również jutro? Chodzi o to, że oprogramowanie zostało zmienione.
5gon12eder,
3
Ponieważ nikt nigdy nie napisze override_function('pow', '$a,$b', 'return $a * $b;');przy zdrowych zmysłach ... ani nie spróbuje przepisać go w celu obsługi liczb zespolonych.
8
Więc ... hmm ... ten "okazał się kodem bezbłędnym" ... ma błąd .
W przypadku takich małych funkcji warto rozważyć przetestowanie właściwości. Testy oparte na właściwościach automatycznie generują przypadki testowe i testy dla wstępnie ustalonych niezmienników. Dostarczają dokumentację za pośrednictwem niezmienników, dzięki czemu są przydatne do trzymania się. Do takich prostych funkcji są idealne.
Martijn,
6
Również ta funkcja jest świetnym kandydatem do zmiany postaci Hornera, (($a*$x + $b)*$x + $c)*$x + $dktórą łatwo się pomylić, ale często jest znacznie szybsza. To, że myślisz, że się nie zmieni, nie znaczy, że się nie zmieni.
Michael Anderson,

Odpowiedzi:

78

Testy regresji

Chodzi o testy regresji .

Wyobraź sobie, że następny programista patrzy na twoją metodę i zauważa, że ​​używasz magicznych liczb. Powiedziano mu, że magiczne liczby są złe, więc tworzy dwie stałe, jedną dla liczby drugiej, drugą dla liczby trzeciej - nie ma nic złego w robieniu tej zmiany; to nie tak, że modyfikował już i tak poprawną implementację.

Rozproszony, odwraca dwie stałe.

Zatwierdza kod i wszystko wydaje się działać dobrze, ponieważ po każdym zatwierdzeniu nie są wykonywane testy regresji.

Pewnego dnia (może być tygodnie później) coś się psuje gdzie indziej. A gdzie indziej mam na myśli całkowicie przeciwną lokalizację bazy kodu, która wydaje się nie mieć nic wspólnego z polynominalfunkcją. Godziny bolesnego debugowania prowadzą do sprawcy. W tym czasie aplikacja nadal nie działa w produkcji, co powoduje wiele problemów dla klientów.

Zachowanie oryginalnych testów, które napisałeś, może zapobiec takiemu bólowi. Rozkojarzony programista popełni kod i prawie natychmiast zobaczy, że coś złamał; taki kod nie dotrze nawet do produkcji. Testy jednostkowe będą dodatkowo bardzo precyzyjne w odniesieniu do lokalizacji błędu . Rozwiązanie tego nie byłoby trudne.

Efekt uboczny ...

W rzeczywistości większość refaktoryzacji opiera się w dużej mierze na testach regresyjnych. Dokonaj małej zmiany. Test. Jeśli mija, wszystko jest w porządku.

Efektem ubocznym jest to, że jeśli nie masz testów, praktycznie każde refaktoryzowanie staje się ogromnym ryzykiem złamania kodu. Biorąc pod uwagę to wiele przypadków, trudno jest już wytłumaczyć kierownictwu, że refaktoryzacja powinna zostać wykonana, jeszcze trudniej byłoby to zrobić po poprzednich próbach refaktoryzacji wprowadzających wiele błędów.

Dysponując kompletnym zestawem testów, zachęcasz do refaktoryzacji, a więc do lepszego, czystszego kodu. Bez ryzyka staje się bardzo kuszące, aby regularnie refaktoryzować więcej.

Zmiany wymagań

Kolejnym istotnym aspektem jest to, że wymagania się zmieniają. Możesz zostać poproszony o obsługę liczb zespolonych i nagle musisz przeszukać dziennik kontroli wersji, aby znaleźć poprzednie testy, przywrócić je i rozpocząć dodawanie nowych testów.

Dlaczego to wszystko kłopot? Po co usuwać testy, aby dodać je później? Mogłeś je przede wszystkim zatrzymać.

Arseni Mourzenko
źródło
Uwaga pedantyczna: nic nie jest wolne od ryzyka. ;)
jpmc26 18.08.15
2
Regresja jest dla mnie najważniejszym powodem testów - nawet jeśli twój kod jasno określa obowiązki i używa tylko tego, co zostało do niego przekazane (np. Brak globali), to zbyt łatwo jest rozbić coś przez przypadek. Testy nie są idealne, ale nadal są świetne (i prawie zawsze zapobiegają ponownemu pojawieniu się tego samego błędu, o czym większość klientów jest niezadowolona). Jeśli Twoje testy działają, nie ma kosztów utrzymania. Jeśli przestaną działać, prawdopodobnie znalazłeś błąd. Pracuję nad starszą aplikacją z tysiącami globali - bez testów nie odważyłbym się wprowadzić wielu niezbędnych zmian.
Luaan,
Kolejna uwaga pedantyczna: niektóre „magiczne” liczby są w porządku, a zastąpienie ich stałymi jest złym pomysłem.
Deduplicator
3
@Deduplicator wiemy o tym, ale wielu szczególnie świeżo wyszkolonych młodych programistów jest wyjątkowo gorliwych z powodu ślepego podążania za mantrami. A „magiczne liczby są złe” jest jedną z nich. Innym jest „wszystko użyte więcej niż jeden raz musi zostać przekształcone w metodę” prowadzącą w skrajnych przypadkach do mnóstwa metod zawierających tylko jedno zdanie (a potem zastanawiają się, dlaczego wydajność nagle spadła, nigdy nie dowiedzieli się o stosach wywołań).
jwenting
@jwenting: Właśnie dodałem tę notatkę, ponieważ MainMa napisała „nie ma nic złego w robieniu tej zmiany”.
Deduplicator
45

Ponieważ nic nie jest tak proste, aby nie było błędów.

Twój kod, na pierwszy rzut oka, wygląda na wolny od błędów. Jest to w rzeczywistości prosta programowa reprezentacja funkcji wielomianowej.

Tyle że ma błąd ...

public function polynominal($a, $b, $c, $d)
{
    return  $a * pow($x, 3) + $b * pow($x, 2) + $c * $x + $d;
}

$xnie jest zdefiniowany jako dane wejściowe do twojego kodu, i w zależności od języka, środowiska wykonawczego lub zakresu, twoja funkcja może nie działać, może powodować nieprawidłowe wyniki lub może spowodować awarię statku kosmicznego .


Uzupełnienie:

Chociaż na razie możesz uważać swój kod za wolny od błędów, trudno jest powiedzieć, jak długo tak pozostanie. Chociaż można argumentować, że napisanie testu dla tak trywialnego fragmentu kodu nie jest tego warte, to po napisaniu testu praca jest wykonywana, a usunięcie go powoduje usunięcie namacalnej bezpiecznej osłony.

Na szczególną uwagę zasługują usługi pokrycia kodu (takie jak coveralls.io), które dają dobre wskazanie pokrycia zapewnianego przez pakiet testowy. Obejmując każdy wiersz kodu, podajesz przyzwoitą miarę ilości (jeśli nie jakości) testów, które wykonujesz. W połączeniu z wieloma małymi testami usługi te przynajmniej mówią, gdzie nie szukać błędu, gdy się to zdarzy.

Ostatecznie, jeśli masz już napisany test, zachowaj go . Ponieważ oszczędność miejsca lub czasu po usunięciu będzie prawdopodobnie znacznie mniejsza niż dług techniczny później, jeśli wystąpi błąd.


źródło
Istnieje alternatywne rozwiązanie: przejście na niedynamiczne i słabe języki. Testy jednostkowe są zazwyczaj obejście niewystarczająco silny weryfikacji kompilacji i niektóre języki są niby dobry w tym en.wikipedia.org/wiki/Agda_(programming_language)
Den
21

Tak. Gdybyśmy mogli powiedzieć ze 100% pewnością, z pewnością: ta funkcja nigdy nie będzie edytowana i nigdy nie będzie działać w kontekście, który mógłby spowodować jej awarię - gdybyśmy mogli tak powiedzieć, moglibyśmy zrezygnować z testów i zaoszczędzić kilka milisekund na każdym Kompilacja CI.

Ale nie możemy. Lub nie możemy z wieloma funkcjami. I łatwiej jest mieć regułę uruchamiania wszystkich testów przez cały czas, niż wkładać wysiłek w dokładne określenie, z jakiego progu ufności jesteśmy zadowoleni i dokładnie, ile mamy pewności co do niezmienności i nieomylności dowolnej funkcji.

A czas przetwarzania jest tani. Te milisekundy zaoszczędzone, a nawet wielokrotnie pomnożone, nie sumują się prawie do wystarczającego uzasadnienia, że ​​każda funkcja wymaga czasu: czy mamy wystarczającą pewność, że nigdy więcej jej nie przetestujemy?

Carl Manaster
źródło
Myślę, że to bardzo dobry punkt, który tu poruszasz. Widzę już dwóch facetów spędzających godziny na kłótniach na temat testowania niewielkiej funkcjonalności.
Kapol,
@Kapol: nie jest argumentem, spotkanie mające na celu ustalenie, który może zostać, a który może zostać, oraz jakich kryteriów należy użyć, aby zdecydować, a także gdzie udokumentować kryteria i kto podpisuje ostateczną decyzję ...
jmoreno
12

Wszystko, co zostało powiedziane w pozostałych odpowiedziach, jest poprawne, ale dodam jeszcze jedną.

Dokumentacja

Testy jednostkowe, jeśli są dobrze napisane, mogą wyjaśnić deweloperowi dokładnie, co robi funkcja, jakie są jej oczekiwania wejścia / wyjścia, a co ważniejsze, jakiego zachowania można się po niej spodziewać.

Może to ułatwić wykrycie błędu i zmniejszyć zamieszanie.

Nie wszyscy pamiętają wielomiany, geometrię, a nawet algebrę :) Ale dobry test jednostkowy sprawdzony w kontroli wersji zapamięta dla mnie.

Na przykład, jak użyteczna może być jako dokumentacja, zobacz wprowadzenie do Jasmine: http://jasmine.github.io/edge/introduction.html Daj mu kilka sekund na załadowanie, a następnie przewiń w dół. Zobaczysz cały Jasmine API udokumentowany jako wynik testu jednostkowego.

[Aktualizacja na podstawie informacji zwrotnych z @Warbo] Testy również są aktualne, ponieważ jeśli nie, nie powiodą się, co na ogół spowoduje błąd kompilacji, jeśli zostanie użyty CI. Dokumentacja zewnętrzna zmienia się niezależnie od kodu i dlatego niekoniecznie jest aktualna.

Brandon
źródło
1
Wykorzystanie testów jako (jednej części) dokumentacji, którą pozostawiłeś jako domyślną, ma dużą zaletę: są one automatycznie sprawdzane. Inne formy dokumentacji, np. objaśniające komentarze lub fragmenty kodu na stronie internetowej mogą się z czasem zmieniać w miarę ewolucji kodu. Testy jednostkowe spowodują awarię, jeśli nie będą już dokładne. Ta strona jaśminu jest tego skrajnym przykładem.
Warbo,
Chciałbym dodać, że niektóre języki, takie jak D i Rust, integrują generowanie dokumentacji z testami jednostkowymi, aby można było skompilować ten sam fragment kodu w teście jednostkowym i włączyć się do dokumentacji HTML
Idan Arye
2

Sprawdzenie autentyczności

Byłem w trudnych warunkach, w których testowanie jest „stratą czasu” podczas budżetowania i harmonogramu, a następnie „podstawową częścią zapewniania jakości”, gdy klient ma do czynienia z błędami, więc moja opinia jest bardziej płynna niż inni.

Masz budżet. Twoim zadaniem jest uzyskanie najlepszego produktu w tym budżecie, bez względu na to, jaką definicję „najlepszego” możesz zeskrobać razem (nie jest to łatwe do zdefiniowania słowo). Koniec opowieści.

Testowanie jest narzędziem w swoim ekwipunku. Powinieneś go użyć, ponieważ jest to dobre narzędzie , z długą historią oszczędzania milionów, a może nawet miliardów dolarów. Jeśli masz szansę, powinieneś dodać testy do tych prostych funkcji. Może kiedyś uratować twoją skórę.

Ale w prawdziwym świecie, z ograniczeniami dotyczącymi budżetu i harmonogramu, może się to nie zdarzyć. Nie bądź niewolnikiem procedury. Funkcje testowe są fajne, ale w pewnym momencie lepiej jest spędzić roboczogodziny na pisaniu dokumentacji programistycznej słowami, a nie kodu, więc następny programista nie potrzebuje tak dużo testów. A może lepiej wydać refaktoryzację bazy kodu, abyś nie musiał być tak trudny jak bestia. A może lepiej poświęcić ten czas na rozmowę z szefem na temat budżetu i harmonogramu, aby lepiej rozumiał, o co licytują, gdy kolejna runda finansowania spadnie na dalszy plan.

Rozwój oprogramowania to równowaga. Zawsze policz koszt alternatywny wszystkiego, co robisz, aby upewnić się, że nie ma lepszego sposobu na spędzenie czasu.

Cort Ammon
źródło
4
W tym przypadku był już test, więc pytanie nie brzmi, czy je napisać, ale czy je zatwierdzić i uruchomić przy każdej kompilacji, wydaniu lub jakimkolwiek harmonogramie przeprowadzania wszystkich testów.
Paŭlo Ebermann
0

Tak, utrzymuj testy, utrzymuj je i utrzymuj.

Testy jednostkowe służą ochronie ciebie (i innych) przed tobą (i sobą).

Dlaczego utrzymywanie testów to dobry pomysł;

  • Sprawdź funkcjonalność poprzednich wymagań w świetle nowych wymagań i dodatkowej funkcjonalności
  • Sprawdź, czy ćwiczenia refaktoryzacyjne są prawidłowe
  • Dokumentacja wewnętrzna - w ten sposób ma być używany kod
  • Testy regresji, rzeczy się zmieniają
    • Czy zmiany psują stary kod?
    • Czy zmiany wymagają dalszych żądań zmiany lub aktualizacji bieżących funkcji lub kodu?
  • Ponieważ testy są już napisane, zachowaj je; wydany już czas i pieniądze pozwolą obniżyć koszty utrzymania w dalszej linii
Niall
źródło
2
Wydaje się, że nie oferuje nic istotnego w porównaniu z innymi udzielonymi odpowiedziami.
1
Wystąpił problem z opublikowaniem go wcześniej, ale z perspektywy czasu jest to miłe podsumowanie. Usunę to.
Niall
-1

Dokumentacja programisty

  • Skąd mam (jako inny programista) wiedzieć, że zostało to przetestowane?
  • Jeśli chcę naprawić błąd w samodzielnej funkcji, skąd mam wiedzieć, że nie wprowadzam błędu, który już rozważałeś?
  • Wskaźnik złożoności: liczba testów może być dobrą miarą złożoności czegoś. Może to oznaczać, że nie należy go dotykać, ponieważ jest dojrzały i stabilny lub że jest wzdęty i sprzężony.

Dokumentacja użytkownika

  • W oczekiwaniu na zły dokument mogę sprawdzić, czy {zero, wartości ujemne, puste zbiory itp.} Są akceptowane i jaka jest oczekiwana wartość zwracana.
  • Daje również dobry przykład tego, jak powinienem używać obiektu / funkcji
sześćdziesiąt stóp
źródło
1
wydaje się, że nie oferuje to nic istotnego w porównaniu z punktami przedstawionymi i wyjaśnionymi we wcześniejszych odpowiedziach. Nawet „ładne podsumowanie” zostało już opublikowane (według mojego smaku nie jest tak miłe, ale no cóż)
komara