Test‑Driven Development (TDD): Concepts, Common Pitfalls, Tool Selection, and Practical Case Studies
This article explores Test‑Driven Development (TDD), explaining its core concepts, common misconceptions, tool choices such as JUnit, Mockito and JaCoCo, and provides practical case studies including a simple calculator and layered architecture examples in both anemic and DDD models.
The article introduces Test‑Driven Development (TDD) as a software development approach that requires writing tests before code, following the red‑green‑refactor cycle.
Basic TDD workflow :
Red: write a failing test.
Green: implement just enough code to pass the test.
Refactor: improve the code while keeping tests green.
It highlights common pitfalls such as confusing unit tests with integration tests, neglecting test speed, and over‑emphasizing 100% coverage.
Technology selection recommends JUnit 5 for unit testing, Mockito for mocking, JaCoCo for coverage, and Allure for reporting.
Case Study 1 – "Strange Calculator" demonstrates TDD step‑by‑step with code snippets:
public class StrangeCalculatorTest {
private StrangeCalculator strangeCalculator;
@BeforeEach
public void setup() { strangeCalculator = new StrangeCalculator(); }
@Test @DisplayName("input > 0, subtract 1")
public void givenGreaterThan0() {
int input = 1, expected = 0;
int result = strangeCalculator.calculate(input);
Assertions.assertEquals(expected, result);
}
// ... other test methods for <0 and =0 cases
}Initial implementation throws UnsupportedOperationException to keep the test red, then gradually adds logic for each condition, finally refactoring into private helper methods.
public class StrangeCalculator {
public int calculate(int input) {
if (input > 0) return input - 1;
if (input < 0) return input + 1;
return 0;
}
// after second iteration, logic changes to handle 0
=100 as -1, etc.
}The article shows how tests are updated when requirements evolve, removing obsolete tests and adding new ones.
Case Study 2 – Three‑layer Anemic Architecture provides unit‑test examples for DAO, Service, and Controller layers.
public interface CmsArticleMapper {
int insert(CmsArticle article);
CmsArticle selectByPrimaryKey(Long id);
// other CRUD methods
} @SpringBootTest
public class CmsArticleMapperTest {
@Resource private CmsArticleMapper mapper;
@Test public void testInsert() {
CmsArticle article = new CmsArticle();
// set fields …
int inserted = mapper.insert(article);
Assertions.assertEquals(1, inserted);
}
// update, select tests …
} @SpringBootTest(classes = {ArticleServiceImpl.class})
public class ArticleServiceImplTest {
@Resource private ArticleService articleService;
@MockBean IdServiceGateway idServiceGateway;
@MockBean CmsArticleMapper cmsArticleMapper;
@Test public void testCreateDraft() {
Mockito.when(idServiceGateway.nextId()).thenReturn("123");
Mockito.when(cmsArticleMapper.insert(Mockito.any())).thenReturn(1);
CreateDraftCmd cmd = new CreateDraftCmd();
cmd.setTitle("test-title");
cmd.setContent("test-content");
articleService.createDraft(cmd);
Mockito.verify(idServiceGateway).nextId();
Mockito.verify(cmsArticleMapper).insert(Mockito.any());
}
// getById test …
} @ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK, classes = {ArticleController.class})
public class ArticleControllerTest {
@Resource private WebApplicationContext wac;
private MockMvc mockMvc;
@MockBean private ArticleService articleService;
@BeforeEach public void setup() { mockMvc = MockMvcBuilders.webAppContextSetup(wac).build(); }
@Test public void testCreateDraft() throws Exception {
CreateDraftCmd cmd = new CreateDraftCmd();
cmd.setTitle("test-controller-title");
cmd.setContent("test-controller-content");
mockMvc.perform(post("/article/createDraft")
.content(new ObjectMapper().writeValueAsString(cmd))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
// testGet …
}These tests illustrate how to mock external dependencies, keep tests fast, and achieve high coverage.
Case Study 3 – DDD‑based TDD shows unit tests for domain entities, value objects, and factories.
public class ArticleEntityTest {
@Test @DisplayName("create draft")
public void testCreateDraft() {
ArticleEntity e = new ArticleEntity();
e.setTitle(new ArticleTitle("title"));
e.setContent(new ArticleContent("content"));
e.createDraft();
Assertions.assertEquals(PublishState.TO_PUBLISH.getCode(), e.getPublishState());
}
// modifyTitle, modifyContent, publish tests …
} public class ArticleTitleTest {
@Test @DisplayName("null throws NPE")
public void whenGivenNull() {
Assertions.assertThrows(NullPointerException.class, () -> new ArticleTitle(null));
}
@Test @DisplayName("length > 64 throws IAE")
public void whenGivenLengthGreaterThan64() {
Assertions.assertThrows(IllegalArgumentException.class, () -> new ArticleTitle("a".repeat(65)));
}
@Test @DisplayName("valid length 64 succeeds")
public void whenGivenLengthEquals64() {
ArticleTitle t = new ArticleTitle("a".repeat(64));
Assertions.assertEquals(64, t.getValue().length());
}
}The article concludes that mastering TDD improves code reliability, reduces bugs, and supports continuous delivery, while emphasizing the need to write focused unit tests, keep them fast, and integrate coverage tools.
JD Tech
Official JD technology sharing platform. All the cutting‑edge JD tech, innovative insights, and open‑source solutions you’re looking for, all in one place.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.