Testowanie II
Cel zajęć
Celem zajęć jest zapoznanie studentów z oceną jakości testów przy wykorzystaniu metryk pokrycia kodu testami (ang. code coverage).
Pokrycie kodu testami
Jak już była mowa na poprzednich zajęciach testy nie są w stanie zagwarantować, że w
oprogramowaniu nie ma błędów. Trzeba jednak się zastanowić, czy można zrobić coś, aby nasze testy znajdowały jak największą liczbę błędów? Czy można w jakiś sposób sprawdzić jakość naszych testów (przez jakość rozumiemy tutaj znalezienie jak największej ilości błędów)? Jednym ze sposobów mierzenia jakości testów jest sprawdzanie pokrycia kodu.
Możemy wyróżnić między innymi:
a) pokrycie wyrażeń/linii (ang. statement/line coverage)
Sprawdza, czy sterowanie przeszło przez każde wyrażenie dostępne w kodzie. Dla kodu:
public int addItems(List items) { if (items != null) {
list.addAll(items); }
return list.size(); }
następujący test gwarantuje 100% pokrycia wyrażeń: @Test
public void testAddItems() {
assertEquals(3, someObject.addItems(new ArrayList())); }
b) pokrycie decyzji (ang. decision coverage)
Sprawdza, czy każde wyrażenie logiczne przyjęło wartość true i false. Całe wyrażenie logiczne jest brane w tym przypadku pod uwagę, bez względu na podwyrażenia logiczne połączone operatorami AND (&&) lub OR (||).
Dla kodu:
public int addItem(Object item) { if (item != null) {
list.add(items); }
return list.size(); }
następujący test gwarantuje 50% pokrycia decyzji, gdyż warunek w instrukcji warunkowej przyjmuje jedynie wartość true:
@Test
public void testAddItems() {
assertEquals(1, someObject.addItem(new Object())); }
c) pokrycie warunków (ang. condition coverage)
Podobobne do pokrycia decyzji, bierze jednak pod uwagę wartośći jakie przyjmują podwyrażenia logiczne.
d) pokrycie ścieżek (ang. path coverage)
Weryfikuje, czy każda możliwa ścieżka wykonania została sprawdzona. Dla kodu:
public int calculate() { int result = 0; if (a > 0) { result += a; } if (b > 0) { result += b; } return result; }
następujący test gwarantuje 25% pokrycia ścieżek, gdyż tylko jedna z czterech możliwych ścieżek została sprawdzona:
@Test
public void testCalculate() {
SomeObject object = new SomeObject(); object.a = 2;
object.b = 0;
assertEquals(2, object.calculate()); }
dopiero następujący test gwarantuje 100% pokrycia ścieżek: @Test
public void testCalculate() {
SomeObject object = new SomeObject(); object.a = 2; object.b = 0; assertEquals(2, object.calculate()); object.a = 0; object.b = 0; assertEquals(0, object.calculate()); object.a = 3; object.b = 1; assertEquals(4, object.calculate()); object.a = 0; object.b = 1; assertEquals(1, object.calculate()); }
e) pokrycie metod (ang. methid coverage)
Sprawdza, czy wszystkie dostępne metody zostały wywołane podczas testowania. f) pokrycie klas (ang. class coverage)
Wybrane problemy
Sprawdzanie pokrycia kodu testami daje nam pewną informację o jakości naszych testów, nie możemy jednak polegać na nim całkowicie. Poniżej przedstawione jest kilka sytuacji pokazujących, że takie podejście może być czasem mylące:
a) 100% pokrycia nie gwarantuje jakości! Dla kodu:
public List addObject(List list, Object o) { if (list != null) {
list.add(o); }
return list; }
następujący test gwarantuje 100% pokrycia wyrażeń, decyzji, warunków, metod, klass, ale nic tak naprawdę nie testuje, bo nie ma w nim żadnej instrukcji assert*:
@Test
public void testAddObject() {
SomeObject object = new SomeObject();
object.addObject(new ArrayList(), new Object()); object.addObject(null, null);
}
Dla kodu:
public int multiply(int x, int y) { return x + y;
}
następujący test gwarantuje 100% pokrycia wyrażeń, decyzji, warunków, metod, klass, ale źle dobrane dane wejściowe powodują, że test nie wykrywa błędu:
@Test
public void testAdd() {
SomeObject object = new SomeObject(); assertEquals(4, object.multiply(2, 2)); }
b) Czasami 100% pokrycie nie może być osiągnięte! Dla kodu:
public List<String> addObject(List<String> list, String newName) { if (list != null) {
for (String name : list) { System.out.println(name); } } System.out.println("---"); if (list != null) { list.add(newName); } return list; }
nie możliwe jest stworzenie testu ze 100% pokryciem ścieżek.
Badanie pokrycia kodu przy pomocy narzędzia EclEmma
EclEmma to wtyczka do Eclipse'a pozwalająca nam sprawdzić pokrycie kodu testami.
1. Zaimportuj projekt z pliku CodeCoverage4Students.zip (File->Import->Existing Project Into Workspace).
2. W widoku Package Explorer znajdź plik BootsTest.java, kliknij na nim PPM i wybierz opcję Coverage As -> JUnit Test:
3. Zostanie uruchomiony test. W widoku JUnit widzimy, że test przeszedł, co jednak z pokryciek kodu testami? W widoku Coverage (jeśli widok nie pojawił się automatycznie możemy go otworzyć przez Window->Show View->Other...) widzimy statystyki.
Dodatkowo gdy otworzymy nasze klasy zauważym, że nasz kod został oznaczony kolorami. I tak:
kolor zielony onacza, że linia została całkowicie pokryta kolor żółty oznacza, że linia została częściowo pokryta
kolor czerwony oznacza, że linia w ogóle nie została wykonana
W naszym przykładzie testujemy klasę Boots, a dokładniej jej metodę printSummary, która jest zdefiniowana w klasie Item. Otwórzmy zatem klasę Item i spójrzy na metodę
printSummary:
public String printSummary() { String summary = ""; if (type == THIS_YEAR) {
summary += "NEW!\n"; }
if (producerName != null && producerName.length() > 0) { summary += "Producer: " + producerName + "\n"; }
int totalDiscountAmount = 0;
for (Discount discount : currentDiscountsList) {
totalDiscountAmount += discount.getPercentage(); } if (totalDiscountAmount > 0) { if (totalDiscountAmount > 30) { totalDiscountAmount = 30; }
summary += "Discount: " + totalDiscountAmount + "%\n"; }
summary += "Final Price: "
+ (price * (1 - (totalDiscountAmount / 100))) + "\n"; summary += printDetails();
return summary; }
Możemy zauważyć, że nasz test nie przechodzi przez wszystkie linie kodu metody oraz nie sprawdza wszystkich wartości wyrażeń logicznych. Należałoby dopisać dodatkowe testy, tak aby pokrycie było jak największe.
Ćwiczenie
Wyobraźmy sobie sytuację, że jesteśmy w trakcie pisania systemu dla sklepu interentowego zajmującego się sprzedażą sprzętu narciarskiego. Częśc systemu mamy już napisaną, brakuje nam jednak kilku testów. Naszym zadaniem będzie dopisanie testów dla istniejących klas, tak aby znaleźć istniejące błędy oraz aby pokrycie kody testami było jak największe .
1. Jeśli jeszcze twego wcześniej nie zrobiłeś to zaimportuj projekt z pliku
CodeCoverage4Students.zip (File->Import->Existing Project Into Workspace).
2. Przejrzyj kod znajdujący się w katalogu src, a następnie napisz dla niego testy i sprawdź dla nich pokrycie kodu. Opis metod znajduje się w komentarzach JavaDoc.