Java > Testing in Java > Test-Driven Development (TDD) > Writing Tests First
TDD Example: Simple Calculator
This example demonstrates Test-Driven Development (TDD) by building a simple calculator with addition functionality. We'll write the test first, watch it fail, implement the functionality, and then watch the test pass. This process ensures that our code is testable and meets the defined requirements.
1. Defining the Requirement
Our requirement is to create a Calculator
class with an add
method that takes two integers as input and returns their sum.
2. Writing the Test First
We create a CalculatorTest
class using JUnit 5. This class contains a testAddTwoPositiveNumbers
method that instantiates a Calculator
, calls the add
method with 2 and 3, and asserts that the result is 5. We also include tests for adding positive and negative numbers and adding two negative numbers. Note that the Calculator
class does not yet exist, so this test will initially fail to compile or run.
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class CalculatorTest {
@Test
void testAddTwoPositiveNumbers() {
Calculator calculator = new Calculator();
int result = calculator.add(2, 3);
assertEquals(5, result, "The sum of 2 and 3 should be 5");
}
@Test
void testAddOnePositiveAndOneNegativeNumber() {
Calculator calculator = new Calculator();
int result = calculator.add(5, -2);
assertEquals(3, result, "The sum of 5 and -2 should be 3");
}
@Test
void testAddTwoNegativeNumbers() {
Calculator calculator = new Calculator();
int result = calculator.add(-1, -1);
assertEquals(-2, result, "The sum of -1 and -1 should be -2");
}
}
3. Running the Test (Expect Failure)
At this stage, if you attempt to run the test, it will fail (likely with a compilation error) because the Calculator
class and the add
method do not exist. This is expected in TDD – we're confirming that the test fails when the functionality is missing.
4. Implementing the Functionality
We create the Calculator
class with the add
method that returns the sum of the two input integers. This is the simplest implementation that satisfies the test.
public class Calculator {
public int add(int a, int b) {
return a + b;
}
}
5. Running the Test (Expect Success)
Now, when you run the CalculatorTest
class, all tests should pass. This confirms that the Calculator
class and its add
method are functioning as expected.
6. Refactoring (Optional)
If necessary, refactor the code to improve its readability, maintainability, or performance. In this simple example, refactoring might not be necessary, but in more complex scenarios, it's an important step.
Concepts Behind the Snippet
This snippet demonstrates the core TDD cycle: Red (Write a failing test), Green (Make the test pass), Refactor (Improve the code). It emphasizes writing tests before writing the actual code, which promotes better design and testability.
Real-Life Use Case
Imagine you are building a feature that handles user authentication. Following TDD, you would first write tests that define the success and failure scenarios (e.g., successful login, invalid password, inactive account) before writing the code that performs the authentication. This ensures that your authentication logic is thoroughly tested and meets all defined requirements.
Best Practices
Interview Tip
Be prepared to explain the TDD cycle (Red-Green-Refactor) and its benefits. Also, be ready to discuss scenarios where TDD might be more or less suitable.
When to Use Them
TDD is particularly beneficial when you have well-defined requirements and want to ensure that your code is highly testable. It's also helpful for complex or critical systems where thorough testing is essential. TDD can be time-consuming, so it may not be appropriate for simple, throwaway projects.
Memory Footprint
The memory footprint of tests themselves is generally small and is reclaimed when the tests complete. However, poorly written tests, especially those that create large objects or databases, can have a significant memory impact. It's important to write efficient tests to avoid memory leaks and performance issues.
Alternatives
Alternative testing approaches include Behavior-Driven Development (BDD), which focuses on describing the behavior of the system, and traditional testing approaches where tests are written after the code. While BDD is a variation of TDD, the traditional method often leads to less testable code and can miss edge cases.
Pros
Cons
FAQ
-
What is the difference between TDD and traditional testing?
In TDD, you write tests before you write the code, while in traditional testing, you write tests after the code is written. TDD encourages a more testable design and helps prevent bugs early in the development process. -
Is TDD always the best approach?
No, TDD is not always the best approach. It can be time-consuming, and it might not be suitable for simple projects or legacy code. However, it's highly beneficial for complex or critical systems where thorough testing is essential.