Best practices for test-driven development

Simplify your application of TDD

Test-driven development has received much attention in the last few years. Many books have been written on the subject, including books by Kent Beck (Test Driven Development), Johannes Link (Unit Testing in Java), and David Astels (Test Driven Development: A Practical Guide). We found test-driven development (TDD) to be an extremely valuable development strategy. However, TDD takes some getting used to before it can be employed effectively. In this article, we share some best practices we discovered that helped facilitate our application of TDD. We'll begin with a background on mock objects, an essential component for our application of TDD, and a topic we refer to throughout the article.

Mock object background/definition

Mock objects have been well covered in various literature, notably in Astels' Test Driven Development: A Practical Guide. We won't repeat that discussion here; instead we provide a brief description of mock objects for readers unfamiliar with them.

First, let's define a production class as a class required for the correct operation of the product under development. Typically, any class shipped with the product is considered a production class. Unit tests are written to test production classes. Production classes often interact with other objects (let's call them collaborators) that are passed in as constructor or method arguments. When unit testing a production class, you should focus on the production class's responsibilities. You can take for granted that the collaborators work as expected. After all, those collaborators have their own unit tests—you don't need to repeat them. You really just want to test that the interaction with those collaborators occurs as expected.

That's where mock objects come in—a mock object takes the collaborator's place during execution of a unit test. In a mock object, you hard-code the behavior you need for a specific unit test of your production class. For example, a production collaborator might interact with an external system (e.g., a database) to return a list of records, while a mock object hard-codes this list in the class definition. Other mock objects might generate boundary-value conditions that would be difficult to simulate otherwise (e.g., a mock object that represents an integer value might return a value of Integer.MAX_VALUE). A mock object contains only the logic required to make a unit test pass. Thus, mock objects tend to be significantly simpler than the real production classes used in your system.

Our mock objects typically return a hard-coded value from method invocations and record information about which methods were called and what parameters were passed to them. We test our production classes in terms of mock objects by passing mock objects to the production class as constructor and method arguments. Typically, testing the production class simply involves verifying that it makes the right method calls on the mock objects and that it correctly interprets the results of those calls.

An illustrative example:

public class CalculationComponent {
   public CalculationComponent (CalculationStrategy calcStrategy) {
      this.calcStrategy = calcStrategy;
   public int doCalculation(int num) {
      return calcStrategy.calculate(num);
public interface CalculationStrategy { 
   int calculate(int num); 
public class MockCalculationStrategy implements CalculationStrategy {
   public boolean calculateWasCalled = false;
   public int num;
   public int calculate(int num) {
      this.calculateWasCalled = true;
      this.num = num;
      return 87;
public class CalculationComponentTest extends TestCase {
   public void test1() {
      MockCalculationStrategy mcs = new MockCalculationStrategy();
      CalculationComponent cc = new CalculationComponent(mcs);
      int result = cc.doCalculation(5);
      assertEquals(5, mcs.num);
      assertEquals(87, result);

1 2 Page 1
Page 1 of 2
How to choose a low-code development platform