为什么TDD不是关于“编写测试”,而是关于“换个方式思考”
\n\n想象一下你在盖房子。你有图纸、材料和施工队。但你开始砌砖之前没有检查地基。一个月后,你发现墙壁出现了裂缝。熟悉吗?在编程中,这被称为“以后修复”。而“以后”通常发生在部署前的凌晨3点。
\n\n测试驱动开发(TDD — Test-Driven Development)是一种彻底颠覆流程的技术。你不是先写代码,再写测试。而是先写测试——然后只写能让测试通过的代码。听起来很疯狂?实际上,它能节省时间、神经,并使代码比山间的清晨空气更干净。
\n\n在本文中,我们将通过具体示例解析Java中的TDD。没有废话——只有代码、红-绿-重构循环和真实场景。我们开始吧。
\n\n什么是TDD以及红-绿-重构循环如何工作
\n\nTDD基于三个简单的步骤,循环重复:
\n\n- \n
- 红 — 编写一个不通过的测试(它应该失败)。 \n
- 绿 — 编写最少的代码以使测试通过。 \n
- 重构 — 改进代码而不改变其行为。测试在此期间保持绿色。 \n
这似乎会减慢开发速度。实际上,它加快了速度。你不再猜测“这能工作吗?”。你知道:它能工作,因为测试是绿色的。
\n\nTDD的主要原则
\n- \n
- 没有测试就不要写一行生产代码。 \n
- 测试应该尽可能简单,并且只测试一种行为。 \n
- 代码只写到足以让测试通过。没有“以防万一,我们再添加这个”的情况。 \n
现在让我们转向Java和JUnit 5——最流行的单元测试框架。
\n\n项目设置:Maven、JUnit 5和结构
\n\n对于示例,我们将使用Maven。在pom.xml中添加依赖:
<dependency>\n <groupId>org.junit.jupiter</groupId>\n <artifactId>junit-jupiter-api</artifactId>\n <version>5.10.0</version>\n <scope>test</scope>\n</dependency>\n\nMaven的标准文件夹结构:
\nsrc/\n main/java/com/example/\n test/java/com/example/\n\n所有测试类放在src/test/java中。测试类的名称通常与被测试类的名称一致,并加上Test后缀。例如,Calculator → CalculatorTest。
第一个TDD循环:从零开始编写计算器
\n\n任务:创建一个方法add(int a, int b),返回两个数的和。按照TDD,我们不是从类开始,而是从测试开始。
步骤1:红 — 编写一个会失败的测试
\n\nimport org.junit.jupiter.api.Test;\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class CalculatorTest {\n\n @Test\n void shouldAddTwoNumbers() {\n Calculator calc = new Calculator();\n int result = calc.add(2, 3);\n assertEquals(5, result);\n }\n}\n\n尝试运行——编译器报错:类Calculator不存在。很好,红灯亮起。
步骤2:绿 — 编写最少的代码
\n\n创建类Calculator,其中包含一个简单返回和的方法:
public class Calculator {\n public int add(int a, int b) {\n return a + b;\n }\n}\n\n运行测试——绿色。最小可行的实现已经生效。
\n\n步骤3:重构
\n\n在这个阶段,代码已经很简单,没有什么需要重构的。但假设我们想重命名方法或添加溢出检查。我们这样做,测试仍然通过。如果有什么东西坏了——测试会失败并立即指出问题。
\n\n