Niedawno czytałem trochę o Programowaniu Literackim i przyszło mi to do głowy ... Dobrze napisane testy, szczególnie specyfikacje w stylu BDD, potrafią lepiej wyjaśnić, co robi kod niż proza, i mają dużą zaletę weryfikacja ich własnej dokładności.
Nigdy nie widziałem testów napisanych zgodnie z testowanym kodem. Czy dzieje się tak tylko dlatego, że języki zwykle nie ułatwiają oddzielania kodu aplikacji i kodu testowego, gdy są zapisywane w tym samym pliku źródłowym (i nikt tego nie ułatwił), czy też istnieje bardziej zasadniczy powód oddzielania kodu testowego od kodu aplikacji?
testing
unit-testing
bdd
literate-programming
Chris Devereux
źródło
źródło
Odpowiedzi:
Jedyną korzyścią, jaką mogę wymyślić w przypadku testów wbudowanych, byłoby zmniejszenie liczby plików do zapisania. W przypadku nowoczesnych IDE to naprawdę nie jest taka wielka sprawa.
Testy wbudowane mają jednak wiele oczywistych wad:
źródło
Mogę wymyślić kilka:
Czytelność. Przeplatanie „prawdziwego” kodu i testów utrudni odczytanie prawdziwego kodu.
Nadęty kod. Mieszanie „prawdziwego” kodu i kodu testowego w te same pliki / klasy / cokolwiek, co może skutkować większymi skompilowanymi plikami itp. Jest to szczególnie ważne w przypadku języków z późnym wiązaniem.
Możesz nie chcieć, aby Twoi klienci / klienci widzieli Twój kod testowy. (Nie podoba mi się ten powód ... ale jeśli pracujesz nad projektem o zamkniętym źródle, kod testowy i tak raczej nie pomoże klientowi).
Teraz możliwe są obejścia każdego z tych problemów. Ale IMO, łatwiej jest nie iść tam.
Warto zauważyć, że na początku programiści Java robili takie rzeczy; np. włączenie
main(...)
metody do klasy w celu ułatwienia testowania. Ten pomysł prawie całkowicie zniknął. Praktyką branżową jest wdrażanie testów osobno przy użyciu pewnego rodzaju ram testowych.Warto również zauważyć, że Literate Programming (według Knutha) nigdy nie przyjęło się w branży inżynierii oprogramowania.
źródło
Właściwie można to traktować jako Design By Contract . Problem polega na tym, że większość języków programowania nie pozwala na pisanie kodu w ten sposób :( Bardzo łatwo jest ręcznie przetestować warunki wstępne, ale warunki pocztowe są prawdziwym wyzwaniem bez zmiany sposobu pisania kodu (ogromny negatywny IMO).
Michael Feathers ma prezentację na ten temat i jest to jeden z wielu sposobów, o których wspomina, że można poprawić jakość kodu.
źródło
Z wielu tych samych powodów, dla których starasz się unikać ścisłego łączenia klas w kodzie, dobrym pomysłem jest również unikanie niepotrzebnego łączenia między testami i kodem.
Tworzenie: Testy i kod mogą być pisane w różnym czasie przez różne osoby.
Kontrola: jeśli testy są używane do określenia wymagań, na pewno chcesz, aby podlegały one różnym zasadom określającym, kto może je zmienić i kiedy nie jest to rzeczywisty kod.
Wielokrotnego użytku : Jeśli umieścisz testy bezpośrednio, nie możesz ich użyć z innym fragmentem kodu.
Wyobraź sobie, że masz fragment kodu, który poprawnie wykonuje zadanie, ale pozostawia wiele do życzenia pod względem wydajności, łatwości konserwacji, cokolwiek. Zdecydujesz się zastąpić ten kod nowym i ulepszonym kodem. Korzystanie z tego samego zestawu testów może pomóc zweryfikować, czy nowy kod daje takie same wyniki jak stary kod.
Możliwość wyboru: Oddzielenie testów od kodu ułatwia wybór testów, które chcesz uruchomić.
Na przykład możesz mieć mały pakiet testów, które dotyczą tylko kodu, nad którym obecnie pracujesz, oraz większy pakiet, który testuje cały projekt.
źródło
Oto kilka dodatkowych powodów, dla których mogę wymyślić:
posiadanie testów w oddzielnej bibliotece ułatwia łączenie tylko tej biblioteki ze środowiskiem testowym, a nie z kodem produkcyjnym (tego można uniknąć w przypadku niektórych preprocesorów, ale po co budować takie rzeczy, gdy łatwiejszym rozwiązaniem jest napisanie testów w osobne miejsce)
testy funkcji, klasy, biblioteki są zwykle pisane z punktu widzenia „użytkowników” (użytkownik tej funkcji / klasy / biblioteki). Taki „kod” jest zwykle zapisywany w osobnym pliku lub bibliotece, a test może być jaśniejszy lub „bardziej realistyczny”, jeśli naśladuje tę sytuację.
źródło
Jeśli testy byłyby wbudowane, konieczne byłoby usunięcie kodu potrzebnego do testowania podczas wysyłania produktu do klienta. Tak więc dodatkowe miejsce, w którym przechowujesz swoje testy, po prostu dzieli kod, którego potrzebujesz i kodu, którego potrzebuje klient .
źródło
Pomysł ten stanowi po prostu metodę „Self_Test” w kontekście projektowania obiektowego lub obiektowego. Jeśli używasz skompilowanego języka obiektowego, takiego jak Ada, cały kod autotestu zostanie oznaczony przez kompilator jako nieużywany (nigdy nie wywołany) podczas kompilacji produkcyjnej, a zatem wszystko zostanie zoptymalizowane - żaden z nich nie pojawi się w wynikowy plik wykonywalny.
Korzystanie z metody „Self_Test” jest niezwykle dobrym pomysłem, a gdyby programiści naprawdę martwili się jakością, wszyscy by to robili. Jedną ważną kwestią jest to, że metoda „Self_Test” wymaga intensywnej dyscypliny, ponieważ nie może uzyskać dostępu do żadnych szczegółów implementacji i zamiast tego musi polegać tylko na wszystkich innych opublikowanych metodach w specyfikacji obiektu. Oczywiście, jeśli autotest nie powiedzie się, implementacja będzie musiała ulec zmianie. Autotest powinien rygorystycznie testować wszystkie opublikowane właściwości metod obiektu, ale nigdy nie polegać w żaden sposób na szczegółach żadnej konkretnej implementacji.
Języki obiektowe i zorientowane obiektowo często zapewniają dokładnie ten rodzaj dyscypliny w odniesieniu do metod zewnętrznych w stosunku do testowanego obiektu (wymuszają specyfikację obiektu, uniemożliwiając dostęp do szczegółów jego implementacji i zgłaszając błąd kompilacji w przypadku wykrycia takiej próby ). Ale wszystkie wewnętrzne metody obiektu mają pełny dostęp do każdego szczegółu implementacji. Tak więc metoda autotestu znajduje się w wyjątkowej sytuacji: ze względu na swój charakter musi być metodą wewnętrzną (autotest jest oczywiście metodą testowanego obiektu), ale musi zostać uwzględniona cała dyscyplina kompilatora metody zewnętrznej ( musi być niezależny od szczegółów implementacji obiektu). Niewiele, jeśli jakiekolwiek języki programowania umożliwiają dyscyplinowanie obiektu ” metoda wewnętrzna, jakby była metodą zewnętrzną. Jest to więc ważny problem projektowania języka programowania.
W przypadku braku odpowiedniej obsługi języka programowania najlepszym sposobem na to jest utworzenie obiektu towarzyszącego. Innymi słowy, dla każdego kodowanego obiektu (nazwijmy go „Big_Object”) tworzysz również drugi obiekt towarzyszący, którego nazwa składa się ze standardowego sufiksu połączonego z nazwą „prawdziwego” obiektu (w tym przypadku „Big_Object_Self_Test ”), a specyfikacja składa się z jednej metody („ Big_Object_Self_Test.Self_Test (This_Big_Object: Big_Object) zwraca wartość logiczną; ”). Obiekt towarzyszący będzie wówczas zależał od specyfikacji głównego obiektu, a kompilator w pełni wymusi całą dyscyplinę tej specyfikacji względem implementacji obiektu towarzyszącego.
źródło
Jest to odpowiedź na dużą liczbę komentarzy sugerujących, że testy wbudowane nie są wykonywane, ponieważ usunięcie kodu testowego z kompilacji wydania jest trudne. To nieprawda. Prawie wszystkie kompilatory i asemblery już to obsługują, w przypadku skompilowanych języków, takich jak C, C ++, C #, odbywa się to za pomocą tak zwanych dyrektyw kompilatora.
W przypadku c # (również uważam, że c ++, składnia może się nieco różnić w zależności od używanego kompilatora), w ten sposób możesz to zrobić.
Ponieważ używa to dyrektyw kompilatora, kod nie będzie istniał w plikach wykonywalnych, które są budowane, jeśli flagi nie są ustawione. W ten sposób tworzysz programy „pisz raz, kompiluj dwa razy” dla wielu platform / sprzętu.
źródło
Używamy testów wbudowanych z naszym kodem Perl. Istnieje moduł Test :: Inline , który generuje pliki testowe z kodu wbudowanego.
Nie jestem szczególnie dobry w organizowaniu moich testów i stwierdziłem, że są one łatwiejsze i istnieje większe prawdopodobieństwo, że zostaną zachowane, gdy zostaną wprowadzone.
W odpowiedzi na kilka podniesionych obaw:
+-- 33 lines: #test----
. Kiedy chcesz pracować z testem, po prostu go rozwiń.Na przykład:
źródło
Erlang 2 faktycznie obsługuje testy wbudowane. Każde wyrażenie boolowskie w kodzie, które nie jest używane (np. Przypisane do zmiennej lub przekazane) jest automatycznie traktowane jako test i oceniane przez kompilator; jeśli wyrażenie jest fałszywe, kod się nie kompiluje.
źródło
Innym powodem oddzielania testów jest to, że często używasz dodatkowych lub nawet innych bibliotek do testowania niż do rzeczywistej implementacji. Jeśli pomieszane zostaną testy i implementacja, kompilator nie może przechwycić przypadkowego użycia bibliotek testowych w implementacji.
Ponadto testy zwykle zawierają znacznie więcej wierszy kodu niż testowane części implementacji, więc będziesz mieć problemy ze znalezieniem implementacji między wszystkimi testami. :-)
źródło
To nie jest prawda. Znacznie lepiej jest umieścić testy jednostkowe obok kodu produkcyjnego, gdy kod produkcyjny, szczególnie gdy procedura produkcyjna jest czysta.
Jeśli tworzysz na przykład w .NET, możesz umieścić kod testowy w zestawie produkcyjnym, a następnie użyć Skalpela, aby usunąć go przed wysyłką.
źródło