Test-driven development (TDD) jest to technika pisania oprogramowania, odwracająca naturalny cykl programowania. Proces tworzenia kodu dzielimy na krótkie iteracje, gdzie najpierw piszemy niedziałający test, potem dodajemy kod przechodzący, a następnie poprawiamy strukturę kodu według oczekiwanych standardów. Te trzy operacje tworzą tzw. mikrocykl TDD, czyli trójstopniowy cykl Red-Green-Refactor.

Jak wygląda pisanie kodu w tej metodyce?
- Zaczynamy od utworzenia testu, który nie będzie przechodził (faza RED)
- Piszemy jak najmniejszą ilość kodu, która zapewni przejście testu, może być to nawet sygnatura metody (faza GREEN)
- Optymalizujemy powstały kod testu lub funkcjonalności (faza REFACTOR)
Powyższe kroki powtarzamy wielokrotnie, aż uzyskamy pożądane funkcjonalności.
*Pisząc kod należy także pamiętać o sprawdzaniu wartości granicznych zgodnie z zasadą CORRECT, która zostanie omówiona w oddzielnym artykule.
Stub, Mock i Spy – przydatne struktury do testowania aplikacji
Stub, mock i spy to rodzaje obiektów zastępczych, które pomagają symulować zachowanie rzeczywistych obiektów w systemie.
Stub zawiera implementacje jakiegoś kodu, którego zachowanie chcemy przetestować. Działa dobrze dla prostych metod i przykładów, jednak przy większej liczbie warunków i możliwości rozrostu interfejsów jego użycie jest kłopotliwe. Implementuje wszystkie metody danego interfejsu.
Mock symuluje zachowanie prawdziwych obiektów i prawdziwego kodu. Jest bardziej elastyczny i funkcjonalny od staba, przy jego implementacji nie jest tworzona osobna klasa (jak w przypadku staba). Co ważne, nie musimy symulować działania wszystkich metod obiektu, lecz tylko te, które chcemy przetestować. Domyślne zachowanie wszystkich metod z mockowanej klasy jest takie, że zwracają wartość domyślną dla swojego typu zwrotu, jeśli nie zostały one jawnie zamockowane. Na mocku można wykonać tylko metody mockowe!
Spy to rodzaj obiektu opakowującego, którego działanie możemy śledzić oraz weryfikować (połączenie prawdziwego obiektu i mocka). Umożliwia on wykorzystanie zarówno prawdziwych metod, jak i metod mockowych (metod mockowych nie można stosować na normalnych obiektach!).
Przykładowy kod z wykorzystaniem stub, mock i spy
public interface EmailService {
void sendEmail(String recipient, String message);
boolean isEmailSent(String recipient);
}
public class UserNotification {
private EmailService emailService;
public UserNotification(EmailService emailService) {
this.emailService = emailService;
}
public void notifyUser(String userEmail, String notificationMessage) {
emailService.sendEmail(userEmail, notificationMessage);
}
public boolean checkEmailSent(String userEmail) {
return emailService.isEmailSent(userEmail);
}
}
Stub
Tworzymy klasę stubową, która implementuje interfejs z wszystkimi metodami.
public class EmailServiceStub implements EmailService {
private boolean emailSent = false;
@Override
public void sendEmail(String recipient, String message) {
emailSent = true;
System.out.println("Stub: Email sent to " + recipient + " with message: " + message);
}
@Override
public boolean isEmailSent(String recipient) {
return emailSent;
}
}
public class UserNotificationTest {
@Test
public void emailStatusBeforeSendingEmailShouldBeFalse() {
//given
EmailService emailServiceStub = new EmailServiceStub();
UserNotification userNotification = new UserNotification(emailServiceStub);
//when
userNotification.checkEmailSent("test@example.com");
//then
assertFalse(userNotification.checkEmailSent("test@example.com"));
}
@Test
public void emailStatusAfterSendingEmailShouldBeTrue() {
//given
EmailService emailServiceStub = new EmailServiceStub();
UserNotification userNotification = new UserNotification(emailServiceStub);
//when
userNotification.notifyUser("test@example.com", "Welcome!");
//then
assertTrue(userNotification.checkEmailSent("test@example.com"));
}
}
Mock
Tworzymy mocka klasy EmailService i mockujemy tylko metodę isEmailSend().
import static org.mockito.Mockito.*;
public class UserNotificationTest {
@Test
public void emailShouldBeSentAfterNotification() {
//given
EmailService emailServiceMock = mock(EmailService.class);
UserNotification userNotification = new UserNotification(emailServiceMock);
//when
userNotification.notifyUser("test@example.com","Welcome");
//then
then(emailServiceMock).should().sendEmail("test@example.com","Welcome");
}
@Test
public void emailStatusShouldBeTrueAfterSendingEmail() {
//given
EmailService emailServiceMock = mock(EmailService.class);
UserNotification userNotification = new UserNotification(emailServiceMock);
given(emailServiceMock.isEmailSent("test@example.com")).willReturn(true);
//when
//then
assertTrue(userNotification.checkEmailSent("test@example.com"));
}
}
Spy
Tworzymy obiekt typu spy, który opakowuje przekazany obiekt emailService. Na tym obiekcie wywołujemy metodę mockową given().willReturn(), czego nie można zrobić dla normalnych obiektów!
import static org.mockito.Mockito.*;
public class UserNotificationTest {
@Test
public void emailStatusShouldBeChangedAfterSpyModification() {
// given
EmailService emailService = new EmailService() {
@Override
public void sendEmail(String recipient, String message) {
System.out.println("Email sent to " + recipient + " with message: " + message);
}
@Override
public boolean isEmailSent(String recipient) {
return false;
}
};
EmailService emailServiceSpy = spy(emailService);
UserNotification userNotification = new UserNotification(emailServiceSpy);
given(emailServiceSpy.isEmailSent("test@example.com")).willReturn(true);
// when
userNotification.notifyUser("test@example.com", "Welcome!");
//then
assertTrue(userNotification.checkEmailSent("test@example.com"));
}
}
