Testowanie jednostkowe statycznie wpisanego kodu funkcjonalnego

15

Chciałem zapytać was ludzi, w których przypadkach sensowne jest testowanie jednostkowe statycznie wpisanego kodu funkcjonalnego, napisanego w haskell, scala, ocaml, nemerle, f # lub haXe (ostatnie jest tym, czym naprawdę jestem zainteresowany, ale chciałem skorzystać z wiedzy większych społeczności).

Proszę o to, ponieważ z mojego zrozumienia:

  • Jednym z aspektów testów jednostkowych jest posiadanie specyfikacji w formie wykonalnej. Jednak jeśli zastosujesz styl deklaratywny, który bezpośrednio odwzorowuje sformalizowane specyfikacje na semantykę języka, czy faktycznie jest nawet możliwe wyrażenie specyfikacji w postaci wykonalnej w oddzielny sposób, co dodaje wartości?

  • Bardziej oczywistym aspektem testów jednostkowych jest wyśledzenie błędów, których nie można wykryć za pomocą analizy statycznej. Biorąc pod uwagę, że bezpieczny kod funkcjonalny typu jest dobrym narzędziem do kodowania bardzo zbliżonego do tego, co rozumie twój analizator statyczny, wydaje się, że możesz znacznie zwiększyć bezpieczeństwo w kierunku analizy statycznej. Jednak prosty błąd, taki jak użycie xzamiast y(oba są współrzędnymi) w kodzie, nie może zostać objęty. OTOH taki błąd może również wystąpić podczas pisania kodu testowego, więc nie jestem pewien, czy warto.

  • Testy jednostkowe wprowadzają redundancję, co oznacza, że ​​gdy wymagania się zmieniają, kod je wdrażający i testy obejmujące ten kod muszą zostać zmienione. To narzut jest oczywiście stały, więc można argumentować, że tak naprawdę to nie ma znaczenia. W rzeczywistości, w językach takich jak Ruby, tak naprawdę nie porównuje się z korzyściami, ale biorąc pod uwagę, jak statyczne programowanie funkcjonalne obejmuje wiele testów jednostek naziemnych, wydaje się, że jest to stały narzut, który można po prostu zmniejszyć bez kary.

Z tego wywnioskuję, że testy jednostkowe są nieco przestarzałe w tym stylu programowania. Oczywiście takie twierdzenie może prowadzić tylko do wojen religijnych, więc sprowadzę to do prostego pytania:

Kiedy używasz takiego stylu programowania, do jakiego stopnia korzystasz z testów jednostkowych i dlaczego (jaką jakość zamierzasz uzyskać dla swojego kodu)? Lub na odwrót: czy masz kryteria, według których możesz zakwalifikować jednostkę statycznie wpisanego kodu funkcjonalnego jako objętą analizatorem statycznym, a zatem bez potrzeby pokrycia testu jednostkowego?

back2dos
źródło
4
Nawiasem mówiąc, jeśli nie próbowałeś QuickCheck , zdecydowanie powinieneś.
Jon Purdy
scalacheck.org jest odpowiednikiem Scala
V-Lamp

Odpowiedzi:

8

Jednym z aspektów testów jednostkowych jest posiadanie specyfikacji w formie wykonalnej. Jednak jeśli zastosujesz styl deklaratywny, który bezpośrednio odwzorowuje sformalizowane specyfikacje na semantykę języka, czy faktycznie jest nawet możliwe wyrażenie specyfikacji w postaci wykonalnej w oddzielny sposób, co dodaje wartości?

Jeśli masz specyfikacje, które można bezpośrednio zamapować na deklaracje funkcji - dobrze. Ale zazwyczaj są to dwa zupełnie różne poziomy abstrakcji. Testy jednostkowe mają na celu przetestowanie pojedynczych fragmentów kodu napisanych jako testy białych skrzynek przez tego samego programistę, który pracuje nad funkcją. Specyfikacje zwykle wyglądają następująco: „kiedy tu wpisuję tę wartość i naciskam ten przycisk, to i to powinno się zdarzyć”. Zazwyczaj taka specyfikacja prowadzi do opracowania i przetestowania znacznie więcej niż jednej funkcji.

Jednak prosty błąd, taki jak użycie x zamiast y (oba są współrzędnymi) w twoim kodzie, nie może zostać uwzględniony. Jednak taki błąd może również wystąpić podczas pisania kodu testowego, więc nie jestem pewien, czy warto.

Twoje błędne przekonanie jest takie, że testy jednostkowe rzeczywiście służą do wykrywania błędów w kodzie z pierwszej ręki - to nieprawda, przynajmniej jest to tylko częściowo prawda. Zostały stworzone, aby uniemożliwić późniejsze wprowadzanie błędów podczas ewolucji kodu. Więc kiedy po raz pierwszy przetestowałeś swoją funkcję i działał test jednostkowy (z poprawnymi „x” i „y”), a następnie podczas refaktoryzacji używasz x zamiast y, wtedy test jednostkowy pokaże to.

Testy jednostkowe wprowadzają redundancję, co oznacza, że ​​gdy wymagania się zmieniają, kod je wdrażający i testy obejmujące ten kod muszą zostać zmienione. To narzut jest oczywiście stały, więc można argumentować, że tak naprawdę to nie ma znaczenia. W rzeczywistości, w językach takich jak Ruby, tak naprawdę nie porównuje się z korzyściami, ale biorąc pod uwagę, jak statyczne programowanie funkcjonalne obejmuje wiele testów jednostek naziemnych, wydaje się, że jest to stały narzut, który można po prostu zmniejszyć bez kary.

W inżynierii większość systemów bezpieczeństwa opiera się na redundancji. Na przykład dwie przerwy w samochodzie, nadmiarowy spadochron dla nurka powietrznego itp. Ten sam pomysł dotyczy testów jednostkowych. Oczywiście posiadanie większej ilości kodu do zmiany, gdy zmieniają się wymagania, może być wadą. Dlatego szczególnie w testach jednostkowych ważne jest, aby utrzymywać je NA SUCHO (zgodnie z zasadą „Dont Repeat Yourself”). W języku o typie statycznym może być konieczne napisanie mniej testów jednostkowych niż w języku o słabym typie. Szczególnie „formalne” testy mogą nie być konieczne - co jest dobrą rzeczą, ponieważ daje więcej czasu na pracę nad ważnymi testami jednostkowymi, które testują istotne rzeczy. I nie myśl, że tylko dlatego, że masz typy statyczne, nie potrzebujesz testów jednostkowych, wciąż jest dużo miejsca na wprowadzenie błędów podczas refaktoryzacji.

Doktor Brown
źródło
5

Jednym z aspektów testów jednostkowych jest posiadanie specyfikacji w formie wykonalnej. Jednak jeśli zastosujesz styl deklaratywny, który bezpośrednio odwzorowuje sformalizowane specyfikacje na semantykę języka, czy faktycznie jest nawet możliwe wyrażenie specyfikacji w postaci wykonalnej w oddzielny sposób, co dodaje wartości?

Jest bardzo mało prawdopodobne, że będziesz w stanie całkowicie wyrazić swoje specyfikacje jako ograniczenia typu.

Kiedy używasz takiego stylu programowania, do jakiego stopnia korzystasz z testów jednostkowych i dlaczego (jaką jakość chcesz uzyskać dla swojego kodu)?

W rzeczywistości jedną z głównych zalet tego stylu jest to, że proste funkcje są łatwiejsze do testowania jednostkowego: nie trzeba ustawiać stanu zewnętrznego ani sprawdzać go po wykonaniu.

Często specyfikację (lub jej część) funkcji można wyrazić jako właściwości odnoszące zwracaną wartość do argumentów. W takim przypadku użycie QuickCheck (dla Haskell) lub ScalaCheck (dla Scala) może pozwolić ci zapisać te właściwości jako wyrażenia języka i sprawdzić, czy przechowują one losowe dane wejściowe.

Aleksiej Romanow
źródło
1
Trochę więcej szczegółów na temat QuickCheck: podstawową ideą jest zapisanie „właściwości” (niezmienników w kodzie) i określenie, jak wygenerować potencjalne dane wejściowe. Quickcheck następnie tworzy mnóstwo losowych danych wejściowych i upewnia się, że niezmiennik zachowuje się w każdym przypadku. Jest to dokładniejsze niż testowanie jednostkowe.
Tikhon Jelvis,
1

Testy jednostkowe można traktować jako przykład użycia kodu wraz z opisem, dlaczego jest on cenny.

Oto przykład, w którym wujek Bob był na tyle miły, że sparował się ze mną w „Game of Life” Johna Conwaya . Myślę, że jest to doskonałe ćwiczenie dla tego rodzaju rzeczy. Większość testów ma pełny system, testuje całą grę, ale ta pierwsza testuje tylko jedną funkcję - tę, która oblicza sąsiadów wokół komórki. Widać, że wszystkie testy są napisane w formie deklaratywnej, a zachowanie, którego szukamy, jest jasno określone.

Możliwe jest także kpienie z funkcji używanych w funkcjach; albo przez przekazanie ich do funkcji (odpowiednik wstrzyknięcia zależności), albo za pomocą frameworków takich jak Midje Briana Maricka .

Lunivore
źródło
0

Tak, testy jednostkowe mają już sens ze statycznie wpisanym kodem funkcjonalnym. Prosty przykład:

prop_encode a = (decode . encode $ a) == a

Możesz wymusić za prop_encodepomocą typu statycznego.

Zhen
źródło