Test Driven Development to technika tworzenia oprogramowania, która odwraca domyślny porządek tworzenia kodu. Najpierw pisane są testy, a dopiero później implementowana jest nowa funkcjonalność. To podejście polega na wielokrotnym powtarzaniu kroków (tzw. cykl: red – green – refactor):
- RED: pierwszym krokiem jest napisanie testu, który ma sprawdzać dodawaną funkcjonalność. Test nie powinien się wykonać pozytywie, ponieważ sama funkcjonalność jeszcze nie jest zaimplementowana.
- GREEN: implementacja danej funkcjonalności, przez której brak test się nie wykonał. Dodajemy tylko tyle kodu, ile jest niezbędne do pozytywnego wykonania testu. Na tym etapie test powinien się udać.
- REFACTOR: refaktoryzacja napisanego kodu. Na tym etapie nie zmieniamy funkcjonalności, jedynie „oczyszamy” kod, np. wydzielamy metodę, usuwamy powielający się kod.
Po zakończeniu cyklu można przejść do następnej iteracji i zacząć cały cykl od początku. Ważne, aby na bieżąco uruchamiać wszystkie testy, żeby mieć kontrolę na tym, czy zmiany, które utworzyliśmy nie powodują nie przechodzenia pozostałych testów.
Przykład: Tworzymy książkę adresową, gdzie będą zapisywane poszczególne kontakty.
TEST 1. Utworzenie klasy testowej – ContactTest– kontakty do książki adresowej.
RED – utworzenie metody testowej shouldNewContactHasAProperDatas(). Sprawdzenie, czy utworzony kontakt posiada imię, które zostało podane. W tym momencie brak jest klasy Contact oraz metody getName().
class ContactRepositoryTest {
@Test
void shouldContactBeAddedToRepository(){
//given
ContactRepository contactRepository = new ContactRepository();
Contact contact = new Contact("Anna", "Nowak", "777888999");
//when
contactRepository.addContact(contact);
}
}
GREEN – utworzenie klasy Contact, konstruktora, metody getName() oraz metody get() dla pozostałych zmiennych. Test się wykonuje poprawnie.
public class Contact {
private String name;
private String surname;
private String telNumber;
public Contact(String name, String surname, String telNumber) {
this.name = name;
this.surname = surname;
this.telNumber = telNumber;
}
public String getName() {
return name;
}
public String getSurname(){
return surname;
}
public String getTelNumber(){
return telNumber;
}
}
REFACTOR – na tym etapie nie potrzeby refaktoryzacji.
TEST 2. Utworzenie klasy testowej ContactRepositoryTest – klasa ContactRepository ma za zadanie przechowywać obiekty klasy Contact – tzw. książka adresowa.
ITERACJA 1:
RED – sprawdzanie będzie, czy kontakty dodają się do listy – shouldContactBeAddedToRepository(). Test się nie wykonuje (brak klasy ContactRepositoryTest). Teraz brakuje klasy ContactRepository oraz metody addContact().
class ContactRepositoryTest {
@Test
void shouldContactBeAddedToRepository(){
//given
ContactRepository contactRepository = new ContactRepository();
Contact contact = new Contact("Anna", "Nowak", "777888999");
//when
contactRepository.addContact(contact);
}
}
GREEN – aby kod przeszedł test, musi zostać dodana klasa ContactRepository oraz metoda addContact() do klasy ContactRepository.
public class ContactRepository {
public void addContact(Contact contact) {
}
}
REFACTOR – na tym etapie nie potrzeby refaktoryzacji.
ITERACJA 2:
RED – sprawdzenie asercji – czy kontakt został dodany do listy. Test nie przechodzi – brakuje metody getAllContacts() oraz ciała metody addContact().
class ContactRepositoryTest {
@Test
void shouldContactBeAddedToRepository(){
//given
ContactRepository contactRepository = new ContactRepository();
Contact contact = new Contact("Anna", "Nowak", "777888999");
//when
contactRepository.addContact(contact);
//then
assertThat(contactRepository.getAllContacts().get(0), is(contact));
}
}
GREEN – utworzenie metody getAllContacts() oraz dodanie ciała metody addContact(), żeby asercja była spełniona i test się udał.
public class ContactRepository {
private List<Contact> contacts = new ArrayList<>();
public void addContact(Contact contact) {
}
public List<Contact> getAllContacts() {
return contacts;
}
}
REFACTOR – w założeniu to nie koniec testowania. Praktycznie do każdego testu będziemy potrzebowali instancji klasy ContactRepository, dlatego możemy je zapisać jako pole w klasie testowej.
class ContactRepositoryTest {
ContactRepository contactRepository = new ContactRepository();
@Test
void shouldContactBeAddedToRepository(){
//given
Contact contact = new Contact("Anna", "Nowak", "777888999");
//when
contactRepository.addContact(contact);
//then
assertThat(contactRepository.getAllContacts().get(0), is(contact));
}
}