Test Driven Development
Test Driven Development jest podejściem do programowania, który polega na pisania testów dla każdej funkcjonalności przed napisaniem kodu głównego (biznesowego). W podejściu tym wyróżniamy trzy etap: RED, GREEN, REFACTOR. W pierwszym etapie napisany test nie powinien powieść się (RED), dopiero wówczas programista przystępuje do pisania kodu głównego, który to spowoduje że test zakończy się sukcesem (GREEN). Nie musi być to perfekcyjny kod, istotne jest aby test został zaliczony. Dopiero w trzecim etapie programista przystępuje do refaktoryzacji kodu (REFACTOR).
ETAP I
@Test
void shouldBeAbleToAddMealToRepository(){
//given
Meal meal = new Meal(13,"mealOne",1);
//when
mealRepository.add(meal);
//then
assertThat(mealRepository.getAllMeals().get(0), is(meal));
}
ETAP II
public void add(Meal meal) {
meals.add(meal);
}
ETAP III
Etap czyszczenia i refaktoringu
TDD jest dyscypliną i zgodnie z jej zasadą, musisz tak pisać kod aby nie dopuścić do sytuacji, w której piszesz testy po zakodowaniu odpowiedniej funkcjonalności. Jeśli twoja aplikacja posiada testy, które zostały napisane po zakończeniu prac nad aplikacją lub programem to znaczy, że nie były przestrzegane regóły TDD.
Powinieneś być świadomy, że postępowanie zgodnie z dyscypliną Test Driven Development to nie jest to samo co programowanie zgodnie z Behavior Driven Development (BDD). Kluczowe kwestia to pisanie testów przed kodowaniem funkcjonalności. Np. pisząc szczegółową specyfikację, która dokładnie określa zachowanie jakiegoś obiektu przed przystapieniem do wdrożenia tej specyfikacji oznacza, że postępujesz zgodnie z zasadą TDD.
ETAP I
@Test
void findMealByPrice(){
//given
Meal meal = new Meal(6,"mealOne");
Meal meal2 = new Meal(13,"mealTwo");
Meal meal3 = new Meal(7,"mealThree");
mealRepository.add(meal);
mealRepository.add(meal2);
mealRepository.add(meal3);
//when;
List<Meal> mealList = mealRepository.findByPrice(7, ValueType.LESS);
List<Meal> mealList2 = mealRepository.findByPrice(7, ValueType.MORE);
List<Meal> mealList3 = mealRepository.findByPrice(7, ValueType.EXACT);
//then
assertThat(mealList.size(),is(1));
assertThat(mealList2.size(),is(1));
assertThat(mealList3.size(),is(1));
}
ETAP II
public List<Meal> findByPrice(int price, ValueType valueType) {
List<Meal> result;
if(valueType==ValueType.MORE){
result= meals.stream().filter(meal -> meal.getPrice() > price).collect(Collectors.toList());
}else if (valueType==ValueType.LESS){
result= meals.stream().filter(meal -> meal.getPrice() < price).collect(Collectors.toList());
}else{
result= meals.stream().filter(meal -> meal.getPrice() == price).collect(Collectors.toList());
}
return result;
}
Pomimo początkowych problemów z kodowaniem zgodnie z TDD takie podejście daje nam dużo kożyści. Poniżej postaram się opisać kilka z nich:
1. WYSOKA JAKOŚĆ KODU
2. CAŁKOWITE POKRYCIE KODU TESTAMI
3. KONCENTRACJA
Jesteś wydajniejszy podczas kodowania, TDD pomaga ci utrzymywać wysoką wydajność przez skupienie twojej uwagi na węższym zagadnieniu. Pisząc nie przechodzący test w pierszym etapie, a następnie próbująć go rozwiązać twoja koncentracja jest całkowicie skupiona na tym konkretnym, wąskim zagadnieniu. To zmusza cię do myślenia o jednej funkcjonalności w danym momencie zamiast o całej aplikacji. Przez co ilość popełnionych błędów oraz skaracasz czas programowania.
4. INTERFEJS
Pisząc test dla pojedynczej funkcjonalności, wcześniej musisz pomyśleć o interfejsie publicznym z którym inni programiści muszą się zintegrować. Z perspektywy testów piszesz tylko metody wywoływane w teście czyli publiczne. To oznacza że kod jest bardziej przejrzysty i lepiej zorganizowany.
5. CZYSTY KOD
Nawiązując do wcześniejszego punktu. Testy współdziałają tylko z publicznymi metodami więc programista ma lepszy obraz które motody mogą być prywatne. Co ogranicza ryzyko upublicznienia metod, które nie powinny być public.
6. BEZPIECZNY REFACTORING
Napisany kod po pozytywnym przejściu testów jest bezpieczniejszy do refactoringu. Jeśli otrzymasz do pracy kod napisany przez innego programistę, zanim zaczniesz pracę z tym kodem, napisz jak najwięcej testów spełniających twoje oczekiwania. Podejście to ułatwi ci refaktoryzację kodu jak rownież pomoże ci w dodawaniu nowych funkcjonalności, rozwiązań jednocześnie gwarantując, że nic nie zostanie zepsute w kodzie.
6. MNIEJ BŁĘDÓW
Pisanie dużych progamów bez pisania testów zwykle kończy się dużą startą czasu na szukaniu bugów czy innych problemów w końcowym etapie projektu. Więcej czasu oznacza więcej pieniędzy, co w ostateczym rozrachunku oznacza wyższy koszt projektu. TDD nie tylko oszczędza czas na naprawie błedów, ale również redukuje koszty wprowadzenia nowych rozwiązań
7. "ŻYWA" DOKUMENTACJA
Testy mogą służyć jako dokumentacja dla developera. W TDD testy zwykle są pisane z założeniem różnych scenariuszy, więc można sprawdzić jakie są oczekiwania danej metody wobec wprowadzanych danych i jaki jest oczekiwany rezultat. Reasumując przypadki testowe dokumentują jak nasz program działa oraz jak dane metody powinny się zachowywać.