Test Driven Development z użyciem JUnit 5. Część 6

Ostatni artykuł z naszej serii poświęconej programowaniu sterowanemu testami za pomocą JUnit 5. Miłej lektury.

Przechodzimy teraz do implementacji klasy PremiumFlight i jej logiki. Stworzymy PremiumFlight jako podklasę Flight i nadpisujemy metody addPassenger i removePassenger, ale zachowują się one jak stubs — nic nie robią i po prostu zwracają fałsz. Ich zachowanie zostanie później przedłużone. Praca w stylu TDD obejmuje najpierw tworzenie testów, a następnie logikę biznesową.

public class PremiumFlight extends Flight { #1

public PremiumFlight(String id) { #2

super(id); #2

} #2

@Override

public boolean addPassenger(Passenger passenger) { #3

return false; #3

} #3

@Override

public boolean removePassenger(Passenger passenger) { #4

return false; #4

} #4

W tym listingu:

  • Deklarujemy klasę PremiumFlight, która rozszerza Flight # 1 i tworzymy dla niej konstruktora # 2.
  • Tworzymy metody addPassenger # 3 i usuwamy metody # 4 jako stubs, bez żadnej logiki biznesowej. Po prostu zwracają fałsz.

Teraz wdrażamy testy zgodnie z logiką biznesową lotów premium z “figueres” 20.8 i 20.9.

public class AirportTest {

[…]

@DisplayName(“Given there is a premium flight”) #1

@Nested #1

class PremiumFlightTest { #1

private Flight premiumFlight; #2

private Passenger mike; #2

private Passenger james; #2

@BeforeEach

void setUp() {

premiumFlight = new PremiumFlight(“3”); #3

mike = new Passenger(“Mike”, false); #3

james = new Passenger(“James”, true); #3

}

@Nested #4

@DisplayName(“When we have a regular passenger”) #4

class RegularPassenger { #4

@Test #5

@DisplayName(“Then you cannot add or remove him #5

from a premium flight”) #5

public void testPremiumFlightRegularPassenger() { #5

assertAll(“Verify all conditions for a regular passenger #6

and a premium flight”, #6

() -> assertEquals(false, #7

premiumFlight.addPassenger(mike)), #7

() -> assertEquals(0, #7

premiumFlight.getPassengersList().size()), #7

() -> assertEquals(false, #8

premiumFlight.removePassenger(mike)), #8

() -> assertEquals(0, #8

premiumFlight.getPassengersList().size()) #8

);

}

}

@Nested #9

@DisplayName(“When we have a VIP passenger”) #9

class VipPassenger { #9

@Test #10

@DisplayName(“Then you can add and remove him #10

from a premium flight”) #10

public void testPremiumFlightVipPassenger() { #10

assertAll(“Verify all conditions for a VIP passenger #11

and a premium flight”, #11

() -> assertEquals(true, #12

premiumFlight.addPassenger(james)), #12

() -> assertEquals(1, #12

premiumFlight.getPassengersList().size()), #12

() -> assertEquals(true, #13

premiumFlight.removePassenger(james)), #13

() -> assertEquals(0, #13

premiumFlight.getPassengersList().size()) #13

);

}

}

}

}

W tym listingu:

  • Deklarujemy zagnieżdżoną klasę PremiumFlightTest # 1, która zawiera pola reprezentujące lot i pasażerów # 2, które są ustawiane przed każdym testem # 3.
  • Tworzymy dwie klasy zagnieżdżone na drugim poziomie w PremiumFlightTest: RegularPassenger # 4 i VipPassenger # 9. Używamy adnotacji JUnit 5 @DisplayName, aby oznaczyć te klasy, zaczynając od słowa kluczowego When.
  • Wstawiamy jeden test do każdej z nowo dodanych klas RegularPassenger # 5 i VipPassenger # 10.
  • Oznaczamy te testy adnotacją JUnit 5 @DisplayName zaczynającą się od słowa kluczowego Then.
  • Testując lot premium i zwykłego pasażera, używamy metody assertAll do weryfikacji wielu warunków # 6. Sprawdzamy, czy nie może dodać pasażera do lotu premium i czy próba dodania pasażera nie zmienia rozmiaru listy pasażerów # 7. Następnie sprawdzamy, czy nie możemy usunąć pasażera z lotu premium i czy próba usunięcia pasażera nie zmienia rozmiaru listy pasażerów nr 8.
  • Testując lot premium i pasażera VIP, ponownie używamy assertAll # 11. Sprawdzamy, czy możemy dodać pasażera do lotu premium i czy to zwiększa rozmiar listy pasażerów # 12. Następnie sprawdzamy, czy możemy usunąć pasażera z lotu premium i czy spowoduje to zmniejszenie rozmiaru listy pasażerów # 13.

Jeden z testów teraz kończy się niepowodzeniem, ale to nie jest problem. Wręcz przeciwnie: tego się spodziewaliśmy. Pamiętaj, że praca w stylu TDD oznacza bycie sterowanym przez testy, więc najpierw tworzymy test, który kończy się niepowodzeniem, a następnie piszemy fragment kodu, który sprawi, że test przejdzie pomyślnie. Ale jest jeszcze jedna niezwykła rzecz: test na lot premium i zwykłego pasażera jest już zielony. Oznacza to, że istniejąca logika biznesowa (metody addPassenger i removePassenger zwracają wartość false) jest wystarczająca w tym przypadku. Rozumiemy, że musimy skupić się tylko na pasażerze VIP. Cytując ponownie Kenta Becka: “TDD pomaga Ci zwracać uwagę na właściwe kwestie we właściwym czasie, dzięki czemu możesz uczynić swoje projekty bardziej przejrzystymi, możesz je udoskonalać w miarę uczenia się. TDD pozwala z czasem zyskać zaufanie do kodu”.

Wracamy więc do klasy PremiumFlight i dodajemy logikę biznesową tylko dla pasażerów VIP. Kierując się testami, przechodzimy od razu do rzeczy.

public class PremiumFlight extends Flight {

public PremiumFlight(String id) {

super(id);

}

@Override

public boolean addPassenger(Passenger passenger) {

if (passenger.isVip()) { #1

return passengers.add(passenger); #1

} #1

return false;

}

@Override

public boolean removePassenger(Passenger passenger) {

if (passenger.isVip()) { #2

return passengers.remove(passenger); #2

} #2

return false;

}

}

W tym listingu:

  • Dodajemy pasażera tylko wtedy, gdy pasażer jest VIPem nr 1.
  • Usuwamy pasażera tylko wtedy, gdy jest to VIP # 2.
  • Testy działają dobrze, a pokrycie kodu wynosi 100%.

Wnioski

W tym artykule omówiono następujące kwestie:

  • Zbadaliśmy koncepcje TDD i pokazaliśmy, w jaki sposób pomaga nam tworzyć bezpieczne aplikacje, ponieważ testy zapobiegają wprowadzaniu błędów do działającego kodu i stanowią część dokumentacji.
  • Przygotowaliśmy aplikację inną niż TDD do przeniesienia do TDD poprzez dodanie hierarchicznych testów JUnit 5, które obejmują istniejącą logikę biznesową.
  • Zrobiliśmy refaktoryzację i poprawę jakości kodu tej aplikacji TDD poprzez zastąpienie warunkowego polimorfizmem w oparciu o opracowane przez nas testy.
  • Wdrażaliśmy nowe funkcje w stylu TDD, zaczynając od pisania testów, a następnie implementując logikę biznesową.

Interesujesz się JUnit? Sprawdź nasze szkolenia

Catalin Tudose
Java and Web Technologies Expert

Originally published at https://www.luxoft-training.pl.