Mateusz Wajnberger

TDD – Test Driven Development

Test Driven Development to technika wytwarzania oprogramowania, w której zanim napiszemy jakąś funkcjonalność najpierw tworzymy do niej test. W TDD mamy do czynienia z cyklami, które składają się z trzech faz: red, green, refactor. Pierwszym krokiem jest napisanie testu, który nie przechodzi. Następnie implementujemy funkcjonalność tak, aby dany test się udał przy czym na tym etapie nie przykładamy jeszcze dużej wagi do jakości kodu. Ostatnim etapem pojedynczego cyklu jest refaktoryzacja kodu jednak musimy być pewni, że nie zostanie zmieniona jego funkcjonalność. Może się zdarzyć, że faza refaktoryzacji nie zawsze jest konieczna.  W celu zilustrowania omawianej metody posłużymy się grą „FizzBuzz”. Cała zabawa polega na tym, że jeżeli liczba naturalna jest podzielna przez trzy to zwracamy „Fizz”, jeżeli jest podzielna przez pięć to zwracamy „Buzz”,  a jeżeli zarówno przez trzy i pięć to zwracamy „FizzBuzz”.  

Tworzymy klasę „FizzBuzz” oraz klasę „FizzBussTest”, w której będziemy testować metodę „process”. Piszemy test, który nie przechodzi.

public class FizzBuzz {

public String process(int i) {
return String.valueOf(i);
}
}

public class FizzBuzzTest {

@Test
public void shouldProcessSingleNumber() {
FizzBuzz fizzBuzz = new FizzBuzz();

assertEquals("1", fizzBuzz.process(1));
assertEquals("2", fizzBuzz.process(2));
assertEquals("Fizz", fizzBuzz.process(3));
}
}

Implementujemy potrzebny kod, aby nasz test przeszedł. 

public String process(int i) {
if(i == 3) {
return "Fizz";
}
return String.valueOf(i);
}

Rezygnujemy z refaktoryzacji działającego kodu więc przechodzimy do napisania kolejnej asercji, po której oczywiście nasz kod nie przechodzi.  W tym momencie zaczyna się kolejny cykl. 

@Test
public void shouldProcessSingleNumber() {
FizzBuzz fizzBuzz = new FizzBuzz();

assertEquals("1", fizzBuzz.process(1));
assertEquals("2", fizzBuzz.process(2));
assertEquals("Fizz", fizzBuzz.process(3));
assertEquals("Buzz", fizzBuzz.process(5));
}

Ponownie implementujemy potrzebny kod, aby nasz test przeszedł. 

public String process(int i) {
if(i == 3) {
return "Fizz";
}
else if (i == 5) {
return "Buzz";
}
return String.valueOf(i);
}

Ponownie rezygnujemy z refaktoryzacji kodu,  a co za tym idzie zaczynamy kolejny cykl, w którym sprawdzamy liczbę podzielną zarówno przez trzy jak i przez pięć. Piszemy kolejną asercję. 

@Test
public void shouldProcessSingleNumber() {
FizzBuzz fizzBuzz = new FizzBuzz();

assertEquals("1", fizzBuzz.process(1));
assertEquals("2", fizzBuzz.process(2));
assertEquals("Fizz", fizzBuzz.process(3));
assertEquals("Buzz", fizzBuzz.process(5));
assertEquals("FizzBuzz", fizzBuzz.process(15));
}

Poprawiamy kod, aby dany test zakończył się powodzeniem. 

public String process(int i) {
if((i % 3 == 0) && (i % 5 == 0)) {
return "FizzBuzz";
} else if(i % 3 == 0) {
return "Fizz";
} else if (i % 5 == 0) {
return "Buzz";
}
return String.valueOf(i);
}

Załóżmy, że w tym miejscu chcielibyśmy zrefaktoryzować nasz funkcjonujący kod ponieważ nie do końca podoba nam się litera „i” jako integer. Jest to trzeci etap poszczególnego cyklu. Na koniec sprawdzamy, czy refaktoryzacja nie wpłynęła negatywnie na funkcjonalność naszego kodu.  

public String process(int number) {
if((number % 3 == 0) && (number % 5 == 0)) {
return "FizzBuzz";
} else if(number % 3 == 0) {
return "Fizz";
} else if (number % 5 == 0) {
return "Buzz";
}
return String.valueOf(number);
}

Podsumowanie

TDD warto stosować z co najmniej kilku powodów. Po pierwsze wymusza to na nas dokładne przemyślenie co nasz kod faktycznie ma robić. W metodyce tej za pomocą testów jednostkowych testujemy małą część kodu co pomaga nam pisać mniejsze objętościowo metody, gdyż duże metody ciężko się testuje. Dzięki testom łatwiej jest nam także utrzymywać kod i rozwijać nasza aplikację ponieważ z każdą naszą kolejną funkcjonalnością możemy sprawdzać czy wszystkie poprzednie testy nadal przechodzą. 

mateusz.wajnberger@gmail.com