Test Driven Development cu JUnit 5. Partea a patra

Luxoft Training
4 min readFeb 4, 2021

--

Cel de-al patrulea articol din cadrul seriei noastre despre Test Driven Development cu JUnit 5. In acest articol vom continua procesul de refactoring al aplicatiei noastre de flight-management application.

4. Refactoring al aplicatiei de flight-management

Vrem sa facem refactoring si sa inlocuim conditional statements cu polymorphism. Elementul cheie in procesul de refactoring este sa orientam designul spre polymorphism in loc de procedural-style conditional code. Cu polymorphism (abilitatea unui obiect de a trece cu bine mai mult de un test IS-A), metoda pe care o apelam nu este determinata de compile-time, ci la runtime, in functie de object type.

Principiul pe baza caruia actionam se numeste open/closed principle. Mai exact, inseamna ca designul din stanga, din imaginea de mai jos, va necesita schimbari la clasele existente de fiecare data cand adaugam un nou tip de zbor. Aceste schimbari se vor reflecta in fiecare decizie conditionla facuta pe baza tipului de zbor. Mai mult decat atat, suntem fortati sa ne bazam pe campul flightType si sa introducem unexecuted default cases.

In designul din dreapta — care este rezultatul procesului de refactoring unde inlocuim conditional cu polimorfism — nu avem nevoie de evaluarea flightType sau de o valoare default in instructiunile de schimbare. Putem chiar sa adaugam un nou tip — hai sa anticipam si sa il denumim PremiumFlight- extinzand base class si definind comportamentul sau. Conform open/closed principle, ierarhia este deschisa pentru extensii (putem adauga cu usurinta noi clase) dar inchisa pentru modificari (clasele existente, incepand cu Flight base class, nu vor fi modificate).

Cum putem fi siguri ca ce facem este corect si ca nu afectam functionalitatea existenta a programului? Raspunsul este ca trecerea cu succes a testelor ne ofera siguranta cu privire la faptul ca functionalitatea existenta nu a suferit modificari. Beneficiile abordarii TDD sunt evidente!

Procesul de refactoring va fi atins prin pastrarea clasei base Flight si, pentru fiecare conditional type, prin adaugarea unei clase separate pentru a extinde Flight. Vom schimba addPassenger si removePassenger in metode abstracte si vom delega implementarea lor catre subclasses. Campul flightType nu mai este important si va fi eliminat.

public abstract class Flight { #1
private String id;
List passengers = new ArrayList(); #2
public Flight(String id) {
this.id = id;
}
public String getId() {
return id;
}
public List getPassengersList() {
return Collections.unmodifiableList(passengers);
}
public abstract boolean addPassenger(Passenger passenger); #3
public abstract boolean removePassenger(Passenger passenger); #3
}

In codul de mai sus:

  • Declaram clasa ca fiind abstracta, si o facem pe baza flight hierarchy #1.
  • Facem lista de pasageri package-private, si ii permitem sa fie mostenita direct de catre subclasses in acelasi pachet #2.
  • Declaram addPassenger si removePassenger ca abstract methods, delegand implementarea lor la subclasses #3.

Introducem EconomyFlight class care extinde Flight si implementeaza metodele abstracte addPassenger si removePassenger.

public class EconomyFlight extends Flight { #1
public EconomyFlight(String id) { #2
super(id); #2
} #2
@Override
public boolean addPassenger(Passenger passenger) { #3
return passengers.add(passenger); #3
} #3
@Override
public boolean removePassenger(Passenger passenger) { #4
if (!passenger.isVip()) { #4
return passengers.remove(passenger); #4
} #4
return false; #4
} #4
}

In codul de mai sus:

  • Declaram EconomyFlight class extinzand Flight abstract class #1 si creem un constructor apeland constructorul superclass #2.
  • Implementam metoda addPassenger conform business logic: adaugam pur si simplu un pasager la un economy flight fara restrictii #3.
  • Implementam metoda removePassenger conform business logic: un pasager poate sa fie scos dintr-un zbor doar daca pasagerul nu este VIP #4.

Introducem si BusinessFlight class care extinde Flight si implementeaza metodele abstracte mostenite addPassenger si removePassenger.

public class BusinessFlight extends Flight { #1
public BusinessFlight(String id) { #2
super(id); #2
} #2
@Override
public boolean addPassenger(Passenger passenger) { #3
if (passenger.isVip()) { #3
return passengers.add(passenger); #3
} #3
return false; #3
} #3
@Override
public boolean removePassenger(Passenger passenger) { #4
return false; #4
} #4
}

In codul de mai sus:

  • Declaram BusinessFlight class extinzand Flight abstract class #1 si creem un constructor apeland constructorul superclass #2.
  • Implementam metoda addPassenger conform business logic: doar un pasager VIP poate sa fie adaugat la un business flight #3.
  • Implementam metoda removePassenger conform cu business logic: un pasager nu poate sa fie scos dintr-un business flight #4.

Refactoring prin inlocuirea conditional cu polymorphism, ne arata ca metodele sunt acum mai scurte si mai clare. De asemenea, nu suntem fortati sa tratam cazul default anterior la care nu ne-am asteptat si care a generat o exceptie. Bineinteles, schimbarile generate de refactoring si API se propaga in teste dupa cum vedem mai jos.

public class AirportTest {
@DisplayName(“Given there is an economy flight”)
@Nested
class EconomyFlightTest {
private Flight economyFlight;
@BeforeEach
void setUp() {
economyFlight = new EconomyFlight(“1”); #1
}
[…]
}
@DisplayName(“Given there is a business flight”)
@Nested
class BusinessFlightTest {
private Flight businessFlight;
@BeforeEach
void setUp() {
businessFlight = new BusinessFlight(“2”); #2
}
[…]
}
}

In codul de mai sus inlocuim instantierile anterioare Flight cu instantieri ale EconomyFlight #1 si BusinessFlight #2. Inlocuim si Airport class care a functionat drept client pentru clasele Passenger si Flight — nu mai este necesar, acum ca am introdus testele. Inainte ne-a ajutat sa declaram principala metoda care a creat diferitele tipuri de flights si passangers si le-a facut sa functioneze impreuna.

Vrei sa inveti mai multe despre aceasta tehnologie? Descopera cursurile noastre.

Catalin Tudose
Java and Web Technologies Expert

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

--

--

No responses yet