Commit 27ab5d91 by Patryk Czarnik

Przykłady testów JUnit 4

parent 03d42bff
...@@ -11,4 +11,14 @@ ...@@ -11,4 +11,14 @@
<maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project> </project>
package testowanie;
public class BrakSrodkow extends Exception {
public BrakSrodkow() {
super();
}
public BrakSrodkow(String message) {
super(message);
}
}
package testowanie;
public class Funkcje {
public static long silnia(int n) {
long wynik = 1;
for(int i = 1; i <= n; i++) {
wynik *= i;
}
return wynik;
}
public static long fib(int n) {
if(n <= 1) return n;
return fib(n-2) + fib(n-1);
}
}
package testowanie;
public class Konto {
private final int numer;
private int saldo;
private String wlasciciel;
public Konto(int numer, int saldo, String wlasciciel) {
this.numer = numer;
this.saldo = saldo;
this.wlasciciel = wlasciciel;
}
public int getNumer() {
return numer;
}
public int getSaldo() {
return saldo;
}
public String getWlasciciel() {
return wlasciciel;
}
@Override
public String toString() {
return "Konto nr " + numer + ", saldo " + saldo + ", wł. " + wlasciciel;
}
public void wplata(int kwota) {
if(kwota <= 0) {
throw new IllegalArgumentException("Niedodatnia kwota w metodzie wplata");
}
this.saldo += kwota;
}
public void wyplata(int kwota) throws BrakSrodkow {
if(kwota <= 0) {
throw new IllegalArgumentException("Niedodatnia kwota w metodzie wyplata");
}
if(kwota > saldo) {
throw new BrakSrodkow("Brak środków na koncie nr " + numer);
}
this.saldo -= kwota;
}
}
package testowanie;
import javax.swing.JOptionPane;
public class Testowanie1_Interaktywnie {
// Jednym ze sposobów, aby przetestować napisane przez nas klasy, metody, funkcje
// jest uruchomić kompletną aplikację, w której tester interaktywnie sprawdza co robi program i jakie wyniki daje.
// Testowanie manualne JEST potrzebne i na pewny etapie musi wystąpić,
// ale stosowanie testownaia manualnego do sprawdzenia czy pojedyncza funkcja działa poprawnie
// jest pracochłonne i kosztowne.
// Wykrycie błędów logicznych, szczególnie obliczeniowych, wymaga dużej uwagi i łatwo takie błędy przeoczyć.
public static void main(String[] args) {
while(true) {
String input = JOptionPane.showInputDialog("Podaj liczbę");
if(input == null) break;
try {
int arg = Integer.parseInt(input);
long silnia = Funkcje.silnia(arg);
long fib = Funkcje.fib(arg);
JOptionPane.showMessageDialog(null,
String.format("silnia(%d) = %d\nfib(%d) = %d",
arg, silnia, arg, fib));
} catch (ArithmeticException | NumberFormatException e) {
JOptionPane.showMessageDialog(null, e, "Błąd", JOptionPane.ERROR_MESSAGE);
}
}
}
}
package testowanie;
public class Testowanie2_Print {
// Jeśli programista, który napisał jakąś funkcję, metodę, klasę ...
// chce tylko sprawdzić czy ta rzecz działa dobrze, może napisać taki prosty programik, który wypisuje coś na ekran.
// I to jest najczęstsze podejście, które jest uważane za złe podejście, przejaw lenistwa itd...
// Napisać i uruchomić jest łatwo, ale:
// - to człowiek sprawdza czy program jest poprawny; NIE MA AUTOMATYCZNEJ ODPOWIEDZI czy jest dobrze czy źle
// - uruchamianie jest jednak bardziej kosztowne niż w testach jednostkowych, bo trzeba spojrzeć na ekran i ocenić czy wyniki / zachowanie są poprawne
public static void main(String[] args) {
long wynik = Funkcje.silnia(5);
System.out.println("silnia(5) = " + wynik);
System.out.println("silnia dla arg 0, 1, 10 : ");
System.out.println(Funkcje.silnia(0));
System.out.println(Funkcje.silnia(1));
System.out.println(Funkcje.silnia(10));
System.out.println();
System.out.println("F(10) = " + Funkcje.fib(10));
}
}
package testowanie;
public class Testowanie3_If {
// Jeśli chcemy, aby program testujący SAM informował, czy testy skończyły się sukcesem, czy porażką,
// można napisać program, który sam w ifach sprawdza wyniki.
// Ten program koncepcyjnie jest już zgodny z ideą testów jednostkowych, chociaż nie używa żadnej biblioteki do testów.
// Patrząc na ten kod aż prosi się o zbudowane pewnego schematu, żeby testy pisać krócej
// i nie robić kopiuj/wklej. Takie schematy istnieją. Najbardziej popularny jest JUnit, inny znany: TestNG.
public static void main(String[] args) {
int ileBledow = 0;
long wynik;
wynik = Funkcje.silnia(0);
if(wynik == 1) {
System.out.println("silnia(0): OK");
} else {
System.out.println("silnia(0): FAIL, zły wynik: " + wynik);
ileBledow++;
}
wynik = Funkcje.silnia(5);
if(wynik == 120) {
System.out.println("silnia(5): OK");
} else {
System.out.println("silnia(5): FAIL, zły wynik: " + wynik);
ileBledow++;
}
wynik = Funkcje.fib(0);
if(wynik == 0) {
System.out.println("fib(0): OK");
} else {
System.out.println("fib(0): FAIL, zły wynik: " + wynik);
ileBledow++;
}
wynik = Funkcje.fib(10);
if(wynik == 55) {
System.out.println("fib(10): OK");
} else {
System.out.println("fib(10): FAIL, zły wynik: " + wynik);
ileBledow++;
}
System.out.println();
if(ileBledow == 0) {
System.out.println("Wszystko OK, nie było błędów");
} else {
System.out.println("Test nie przeszły, było " + ileBledow + " błedów!");
}
}
}
Ciąg dalszy - testy w oparciu i JUnit - jest w katalogu src/test/java
package testowanie;
import static org.junit.Assert.*;
import org.junit.Test;
public class FunkcjeTest {
@Test
public void testSilnia0() {
// normalnie w testach nie robi się żadnych printów
// tu tylko eksperyment, żeby zobaczyć, że ten kod naprawdę się wykonuje
System.out.println("a kuku");
// W typowym teście wykonujemy scenariusz testowy - tutaj tylko wywołanie funkcji, ale bywają bardziej rozbudowane przykłady
long wynik = Funkcje.silnia(0);
// A pod koniec testu za pomocą asercji sprawdzamy czy kod zadziałał prawidłowo.
// Tutaj sprawdzamy wynik, ale często sprawdza się też stan obiektów itp. rzeczy
// W języku Java istnieje prosta konstrukcja assert:
// assert wynik == 1;
// ale rzadko się jej używa, bo daje marne komunikaty w razie błędu; tylko numer linii
// można podać własny komunikat, ale jest to dużo roboty:
// assert wynik == 1 : "dla silnia 0 wyszedł zły wynik " + wynik;
// Najczęście używa się assertEquals pochodzącej z biblioteki JUnit,
assertEquals(1, wynik);
// albo dedykowanych bibliotek do pisania asercji, np. AssertJ
// Technicznie to działa tak:
// - gdy asercja stwierdza, że warunek jest spełniony (jest OK), to nic się dzieje i test idzie dalej
// - gdy asercja stwierdza, że warunek nie jest spełniony, coś się nie zgadza,
// to wyrzucany jest wyjątek AssertionError i dalsza część testu się nie wykona
// Gdy cały test dojdzie do końca bez wyjątku, to jest sukces.
// Gdy test kończy się wyjątkiem AssertionError - to "failuje"
// Gdy test kończy się innym wyjątkiem (np. NPE) - jest to liczone jako "error"
}
@Test
public void testSilnia() {
long wynik = Funkcje.silnia(5);
assertEquals(120, wynik);
}
@Test
public void testFib() {
long wynik = Funkcje.fib(10);
assertEquals(55, wynik);
}
}
package testowanie;
import static org.junit.Assert.*;
import org.junit.Test;
public class KontoTest {
@Test
public void testConstructor() {
Konto konto = new Konto(1234, 1000, "Ala Kowalska");
assertEquals(1234, konto.getNumer());
assertEquals(1000, konto.getSaldo());
assertEquals("Ala Kowalska", konto.getWlasciciel());
}
// Test-Driven Development (TDD)
// to taki sposób pracy (metodologia pracy), że
// najpierw piszemy test, który nie działa ("fejluje")
// a dopiero potem uzupełniamy implementację do momentu, aż test "zaskoczy"
@Test
public void testToString() {
Konto konto = new Konto(1234, 1000, "Ala Kowalska");
assertEquals("Konto nr 1234, saldo 1000, wł. Ala Kowalska", konto.toString());
}
@Test
public void testWplata() {
Konto konto = new Konto(1234, 1000, "Ala Kowalska");
konto.wplata(300);
assertEquals(1300, konto.getSaldo());
}
@Test
public void testWyplata() throws BrakSrodkow {
Konto konto = new Konto(1234, 1000, "Ala Kowalska");
konto.wyplata(400);
assertEquals(600, konto.getSaldo());
}
}
package testowanie;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
public class KontoTest_WersjaRozszerzona {
private Konto konto;
@Before
public void setUp() {
// ta metoda zostanie uruchomiona bezpośrednio przed każdym testem
// dokonuje się w niej inicjalizacji danych potrzebnych do testu - "stan początkowy" / "fixture"
konto = new Konto(1234, 1000, "Ala Kowalska");
}
@Test
public void testConstructor() {
assertEquals(1234, konto.getNumer());
assertEquals(1000, konto.getSaldo());
assertEquals("Ala Kowalska", konto.getWlasciciel());
}
@Test
public void testToString() {
assertEquals("Konto nr 1234, saldo 1000, wł. Ala Kowalska", konto.toString());
}
@Test
public void testWplata() {
konto.wplata(300);
assertEquals(1300, konto.getSaldo());
}
@Test
public void testWyplata() throws BrakSrodkow {
konto.wyplata(400);
assertEquals(600, konto.getSaldo());
}
// Temat sprawdzania wyjątków w testach.
// Aby stwierdzić, że w pewnej sytuacji metoda POWINNA skończyć się wyjątkiem,
// możemy użyć odpowiednio try-catch (testWplataTry)
// albo użyć dedykowanych rozwiązań JUnit - dalsze przykłady
@Test
public void testWplataTry() {
try {
konto.wplata(-100);
// Gdyby nie było wyjątku i program doszedłby do tego miejsca,
// test powinien skończyć się porażką
fail("Powinien być wyjątek, a nie ma.");
} catch(IllegalArgumentException e) {
// Jeśli wyłapiemy wyjątek, możemy sprawdzić w teście jego szczegóły,
// np. jaki jest message (bardzo rzadko takie rzeczy sprawdza się w testach, ale można):
assertEquals("Niedodatnia kwota w metodzie wplata", e.getMessage());
} finally {
// Możemy też upewnić się, że w przypadku wystąpienia wyjątku stan konta się nie zmienił.
assertEquals(1000, konto.getSaldo());
}
}
// Sposób JUnit4 : parametr expcted w adnotacji Test
@Test(expected = IllegalArgumentException.class)
public void testWplataUjemna_v0() {
konto.wplata(-100);
}
// Analogiczny test można też napisać wykorzystując nową funkcjonalność JUnit 5:
@Test
public void testWplataUjemna_v1() {
// assertThrows wykonuje podany fragment kodu (w praktyce podaje się wyrażenie lambda)
// i sprawdza czy ten kończy się podanym rodzajem wyjątku.
// Jeśli jest wyjątek - dobrze
// Jeśli nie ma wyjątku - test failuje
assertThrows(IllegalArgumentException.class, () -> {
konto.wplata(-100);
});
// Dodatkowo po wykonaniu assertThrows możemy sprawdzić jaki jest stan końcowy,
// np. czy saldo się nie zmieniło.
assertEquals(1000, konto.getSaldo());
}
// Aby sprawdzić jaki jest message w wyjątku itp, możemy odebrać obiekt wyjątku i sprawdzić bezpośrednio
@Test
public void testWplataUjemna_v2() {
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
konto.wplata(-100);
});
assertEquals("Niedodatnia kwota w metodzie wplata", exception.getMessage());
assertEquals(1000, konto.getSaldo());
}
@Test
public void testWyplataUjemna() {
assertThrows(IllegalArgumentException.class, () -> {
konto.wyplata(-100);
});
assertEquals(1000, konto.getSaldo());
}
@Test
public void testBrakSrodkow() {
assertThrows(BrakSrodkow.class, () -> {
konto.wyplata(1300);
});
assertEquals(1000, konto.getSaldo());
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment