Test-Driven Development (TDD)
What is TDD?
Test-Driven Development is a software development approach where you write tests before writing the actual code. The TDD cycle follows these steps:
- Red: Write a failing test
- Green: Write the minimum code to make the test pass
- Refactor: Improve the code while keeping tests green
TDD Example
Step 1: Write a Failing Test
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class StringCalculatorTest {
@Test
void add_EmptyString_ReturnsZero() {
StringCalculator calculator = new StringCalculator();
assertEquals(0, calculator.add(""));
}
}
Step 2: Write Minimum Code to Pass
public class StringCalculator {
public int add(String numbers) {
return 0;
}
}
Step 3: Add More Tests
@Test
void add_SingleNumber_ReturnsThatNumber() {
StringCalculator calculator = new StringCalculator();
assertEquals(1, calculator.add("1"));
}
@Test
void add_TwoNumbers_ReturnsSum() {
StringCalculator calculator = new StringCalculator();
assertEquals(3, calculator.add("1,2"));
}
Step 4: Implement the Feature
public class StringCalculator {
public int add(String numbers) {
if (numbers.isEmpty()) {
return 0;
}
String[] nums = numbers.split(",");
int sum = 0;
for (String num : nums) {
sum += Integer.parseInt(num.trim());
}
return sum;
}
}
TDD Benefits
- Better Design: Tests drive the design of the code
- Documentation: Tests serve as living documentation
- Confidence: High test coverage provides confidence in changes
- Maintainability: Easier to maintain and refactor code
- Bug Prevention: Catches issues early in development
TDD Best Practices
Test First
- Write tests before implementing features
- Start with the simplest test case
- Focus on behavior, not implementation
- Use descriptive test names
Test Independence
- Each test should be independent
- Tests should not depend on each other
- Clean up after each test
- Use appropriate test fixtures
Refactoring
- Refactor only when tests are green
- Keep refactoring small and focused
- Run tests after each refactoring
- Maintain test readability
Common TDD Patterns
Test Doubles
import org.mockito.Mock;
import org.mockito.Mockito;
import static org.mockito.Mockito.*;
public class OrderServiceTest {
@Mock
private PaymentGateway paymentGateway;
@Test
void processOrder_ValidPayment_ReturnsSuccess() {
when(paymentGateway.processPayment(anyDouble()))
.thenReturn(true);
OrderService service = new OrderService(paymentGateway);
assertTrue(service.processOrder(new Order(100.0)));
}
}
Behavior-Driven Development (BDD)
@DisplayName("User Registration")
class UserRegistrationTest {
@Test
@DisplayName("Should create new user with valid data")
void shouldCreateNewUserWithValidData() {
// Given
UserRegistration registration = new UserRegistration();
UserData userData = new UserData("john@example.com", "password123");
// When
User user = registration.register(userData);
// Then
assertNotNull(user);
assertEquals("john@example.com", user.getEmail());
}
}