Java Language
Testów jednostkowych
Szukaj…
Wprowadzenie
Uwagi
Ramy testów jednostkowych
Istnieje wiele struktur dostępnych do testowania jednostek w Javie. Zdecydowanie najpopularniejszą opcją jest JUnit. Jest to udokumentowane w następujący sposób:
JUnit4 - Proponowany znacznik dla funkcji JUnit4; jeszcze nie zaimplementowane .
Istnieją inne struktury testów jednostkowych i mają dostępną dokumentację:
Narzędzia do testowania jednostek
Istnieje kilka innych narzędzi używanych do testowania jednostek:
Mockito - Mocking framework; pozwala naśladować obiekty. Przydatne do naśladowania oczekiwanego zachowania jednostki zewnętrznej w teście danej jednostki, aby nie powiązać zachowania jednostki zewnętrznej z testami danej jednostki.
JBehave - Framework BDD . Umożliwia powiązanie testów z zachowaniami użytkownika (umożliwiając sprawdzenie wymagań / scenariusza). Brak znacznika dokumentów w momencie pisania; tutaj jest link zewnętrzny .
Co to jest testowanie jednostkowe?
To jest trochę podkładu. Przeważnie jest to umieszczane, ponieważ dokumentacja jest zmuszona do podania przykładu, nawet jeśli jest przeznaczony jako artykuł pośredniczący. Jeśli znasz już podstawy testowania jednostek, możesz przejść do uwag, w których wymieniono konkretne ramy.
Testy jednostkowe zapewniają, że dany moduł zachowuje się zgodnie z oczekiwaniami. W aplikacjach na dużą skalę zapewnienie odpowiedniego wykonania modułów w próżni jest integralną częścią zapewnienia wierności aplikacji.
Rozważ następujący (trywialny) pseudo-przykład:
public class Example {
public static void main (String args[]) {
new Example();
}
// Application-level test.
public Example() {
Consumer c = new Consumer();
System.out.println("VALUE = " + c.getVal());
}
// Your Module.
class Consumer {
private Capitalizer c;
public Consumer() {
c = new Capitalizer();
}
public String getVal() {
return c.getVal();
}
}
// Another team's module.
class Capitalizer {
private DataReader dr;
public Capitalizer() {
dr = new DataReader();
}
public String getVal() {
return dr.readVal().toUpperCase();
}
}
// Another team's module.
class DataReader {
public String readVal() {
// Refers to a file somewhere in your application deployment, or
// perhaps retrieved over a deployment-specific network.
File f;
String s = "data";
// ... Read data from f into s ...
return s;
}
}
}
Ten przykład jest trywialny; DataReader
pobiera dane z pliku, przekazuje je do Capitalizer
, który konwertuje wszystkie znaki na wielkie litery, które następnie są przekazywane do Consumer
. Ale DataReader
jest silnie powiązany z naszym środowiskiem aplikacji, dlatego odraczamy testowanie tego łańcucha, dopóki nie będziemy gotowi do wdrożenia wersji testowej.
Załóżmy, że gdzieś po drodze w wydaniu, z nieznanych przyczyn, metoda getVal()
w Capitalizer
zmieniła się z zwracania ciągu toUpperCase()
ciąg toLowerCase()
:
// Another team's module.
class Capitalizer {
...
public String getVal() {
return dr.readVal().toLowerCase();
}
}
Oczywiście łamie to oczekiwane zachowanie. Jednak ze względu na uciążliwe procesy związane z wykonaniem DataReader
nie zauważymy tego do następnego wdrożenia testowego. Więc dni / tygodnie / miesiące mijają z tym błędem w naszym systemie, a następnie kierownik produktu to widzi i natychmiast zwraca się do Ciebie, lidera zespołu związanego z Consumer
. „Dlaczego tak się dzieje? Co zmieniliście?” Oczywiście nie masz pojęcia. Nie masz pojęcia, co się dzieje. Nie zmieniłeś żadnego kodu, który powinien tego dotykać; dlaczego jest nagle zepsuty?
Ostatecznie, po dyskusji między zespołami i współpracą, problem zostaje prześledzony, a problem rozwiązany. Ale nasuwa się pytanie; jak można temu zapobiec?
Istnieją dwie oczywiste rzeczy:
Testy muszą być zautomatyzowane
Nasze poleganie na testach ręcznych pozwoliło temu błędowi przejść niezauważalnie o wiele za długo. Potrzebujemy sposobu na zautomatyzowanie procesu natychmiastowego wprowadzania błędów. Nie za 5 tygodni. Nie za 5 dni. Nie za 5 minut. Teraz.
Musisz docenić, że w tym przykładzie wyraziłem jeden bardzo trywialny błąd, który został wprowadzony i niezauważony. W zastosowaniach przemysłowych, z dziesięcioma modułami stale aktualizowanymi, mogą one wkradać się wszędzie. Naprawiasz coś za pomocą jednego modułu, tylko po to, aby zdać sobie sprawę, że na to zachowanie, które „naprawiłeś” polegało w jakiś sposób w innym miejscu (wewnętrznie lub zewnętrznie).
Bez rygorystycznej walidacji rzeczy wkradną się do systemu. Możliwe, że jeśli zaniedbanie będzie wystarczająco duże, spowoduje to tak dużo dodatkowej pracy przy próbach naprawienia zmian (a następnie naprawienia tych poprawek itp.), Że produkt faktycznie zwiększy pozostałą pracę wraz z włożeniem w to wysiłku. Nie chcesz być w takiej sytuacji.
Testy muszą być drobnoziarniste
Drugi problem zauważony w naszym powyższym przykładzie to czas potrzebny na wykrycie błędu. Menedżer produktu wysłał ping do ciebie, gdy testerzy to zauważyli, zbadałeś i stwierdziłeś, że Capitalizer
zwraca pozornie złe dane, wysłałeś ping do zespołu Capitalizer
swoimi odkryciami, zbadali itp. Itd. Itp.
Ten sam punkt, o którym wspomniałem powyżej, na temat ilości i trudności tego banalnego przykładu, trzymaj się tutaj. Oczywiście każdy, kto jest dość dobrze obeznany z Javą, może szybko znaleźć wprowadzony problem. Ale często znacznie, znacznie trudniej jest wyśledzić i zgłosić problemy. Może zespół Capitalizer
dostarczył ci plik JAR bez źródła. Być może znajdują się po drugiej stronie świata, a godziny komunikacji są bardzo ograniczone (być może na e-maile wysyłane raz dziennie). Śledzenie błędów może zająć tygodnie lub dłużej (i znowu, może być ich kilka w danym wydaniu).
W celu złagodzenia przed tym, chcemy rygorystycznym testom, gdy grzywny na poziomie, jak to możliwe (również chcesz gruboziarnistych testowania w celu zapewnienia moduły współdziałają poprawnie, ale to nie nasza punktem tutaj). Chcemy rygorystycznie określić sposób działania wszystkich zewnętrznych funkcji (przynajmniej) i przetestować tę funkcjonalność.
Wejdź w testowanie jednostkowe
Wyobraź sobie, że mieliśmy test, w szczególności upewniając się, że metoda getVal()
Capitalizer
zwróciła napis pisany wielką literą dla danego ciągu wejściowego. Ponadto wyobraź sobie, że test został uruchomiony, zanim jeszcze popełniliśmy kod. Błąd wprowadzony do systemu ( toUpperCase()
zastąpiony przez toLowerCase()
) nie spowoduje żadnych problemów, ponieważ błąd nigdy nie zostanie wprowadzony do systemu. Złapiemy go w teście, programista (miejmy nadzieję) zda sobie sprawę ze swojego błędu i zostanie osiągnięte alternatywne rozwiązanie, w jaki sposób wprowadzić zamierzony efekt.
Dokonano tutaj pewnych zaniedbań, jak wdrożyć te testy, ale są one omówione w dokumentacji specyficznej dla ram (połączonej w uwagach). Mamy nadzieję, że stanowi to przykład tego, dlaczego testy jednostkowe są ważne.