为什么我们应该考虑 TDD?
为什么我希望我的代码由测试驱动?我希望我的代码由需求和满意的客户驱动!你可能听说过 TDD 这个术语,但对它感到不自在。当我第一次听说 TDD 这个词时,我也有点不习惯。为什么要浪费时间编写测试代码来测试还不存在的解决方案代码呢?说真的,我需要编写解决业务问题的实际代码,而你却要我先编写测试代码?事实上,我培训过并与之共事过的一些开发人员也曾遇到过同样的问题,而正是这个问题阻碍了他们对 TDD 产生兴趣!
当我开始软件开发职业生涯时,我在一家小公司工作,公司要求我们尽快交付成果,迭代次数很少。光是考虑为我快速编写的代码编写自动测试就浪费了大量时间!因此,当我第一次读到 TDD 时,我并不感兴趣。我忽略了我的肉丸面条代码;我所关心的只是确保客户在最短的时间内得到预期的业务结果。至于那些糟糕的代码会导致的倒退,就当作以后的问题来解决吧。我需要尽快让客户满意,也就是现在。这可能是我职业生涯中犯下的最短视的错误之一。大多数时候,我和我的同事们不得不自己添加功能和维护一碗乱七八糟的意大利面条。一次又一次,当我们看到自己弄得一团糟时,我们会痛恨过去的自己。在软件开发人员职业生涯的早期,我们犯过很多错误,效率低下,目光短浅。值得庆幸的是,我们并不是第一个遇到这些问题的人。我们可以遵循一些流程来帮助我们提高软件质量,其中之一就是 TDD。
现在,在犯了这么多错误、经历了这么多失败、参与了数百个关键业务软件项目之后,我甚至无法想象不按照 TDD 编写测试的日子。在做项目时,如果不知道我的自动化测试是通过了还是失败了,我想我甚至无法在晚上正常入睡;至少我还有测试!
想象一下,在手机上创建一个 "打扫我家" 的待办事项列表:上面只有一个项目,那就是 清洗咖啡机。你写下这个项目后,分心忘记了它,然后继续一天的工作。当你再次查看清单时,你会发现自己还没有清洗咖啡机!于是,你就继续清洗咖啡机,并将该项目标记为已完成。
这有点像 TDD 的工作方式。你先编写一个失败的测试,然后编写通过测试的代码—在待办事项列表中,你写下 "清洗咖啡机";当你真正清洗了咖啡机后,你再把它从列表中划掉。
在做任何事情之前,我是说现在,你需要明白,一开始测试失败是非常正常的,你需要非常适应并接受它。这就像在手机上写下咖啡机检查清单项目一样。一旦你把它添加到待办事项列表中,这个待办事项列表就是失败的,直到你把这个待办事项标记为 "已完成" 才算通过。在编写通过测试的程序之前,你需要先编写失败的测试。这是 红色、绿色、重构(RGR) 概念的一部分,我们将在第 7 章 "使用 BDD 和 TDD 构建解决方案代码" 中进一步讨论这一概念。 |
回到手机上,您又在清单上添加了更多的项目:打扫厨房、打扫卧室、打扫浴室……然后您去健身房,但却分心了。你想起了自己的清单,想知道自己在出门之前是否真的打扫了家里,于是你查看了自己的待办事项清单。你意识到自己只完成了清单上的一项,你必须回去完成其他任务,才能完全满足 "打扫我的家" 待办事项清单的要求。回家后,您就可以继续打扫房间,在待办事项清单上打勾了:

您可以将待办事项列表中未完成的项目视为失败的测试。打扫卫生的动作就是编写代码,以满足未完成的待办事项。你完成了打扫卧室或浴室的任务,就相当于通过了测试。现在想象一下,你已经完成了所有的清洁工作,并在手机上的 "清洁我的家" 列表中将所有项目都标记为 "已勾选":你完成了!

现在,你可以把 "清理我的家" 列表想象成一个测试。为满足较小的单元测试和集成测试(测试类型将在第 7 章 "使用 BDD 和 TDD 构建解决方案代码" 中详细讨论)而构建的代码的整体完整性将满足您的测试要求。我们可以将 "清理我的家" 列表视为一个测试。该测试贯穿了清洁家庭的所有过程。其中的一些对象涉及清洁浴室,一些涉及清洁厨房,等等。就像我们在编写待办事项列表时一样,你首先要编写代表大局的失败测试,而不是更小、更详细的测试:
// Test Method
public function testCanCleanMyHome()
{
$isMyHomeClean = $this->getCleaner()->clean();
$this->assertTrue($isMyHomeClean);
}
在编写了失败的 "打扫我的家" 测试后,我们就可以开始编写解决方案中较小部分的失败测试了:
// Test Method
public function testCanCleanCoffeeMachine()
{
$isMyCoffeeMachineClean = $this->getCleaner()->clean();
$this->assertTrue($isMyCoffeeMachineClean);
}
现在想象一下,在打扫完家里的卫生后,您把卧室弄得一团糟,而您又没有勾选清单上的 "打扫卧室" 项目。严格来说,你的 "打扫我的家" 待办事项清单现在又不完整了。在通过所有测试后,如果团队中有人或你修改了代码并改变了预期行为,也会发生同样的情况。如果这时运行 testCanCleanMyHome()
测试,就会失败。如果我们在将代码部署到生产环境之前运行这些自动化测试,就能及早发现问题!这样就能更容易地捕捉到破坏预期行为的代码变更!
这虽然过于简单化,但随着我们的深入,你会发现 TDD 就是这样的。毕竟,这并不是一项浪费时间的糟糕练习!
我们是人,我们往往会犯错—至少我是这么认为的。如果你认为自己不会犯错,那你还不如把键盘上的 Delete 键撬出来,因为你根本不需要它。我犯了很多错误,为了帮助我建立对代码的信心,我确保通过所有测试,并让同行评审代码。
实施 TDD 并对软件进行大量的测试覆盖,是帮助你和你的团队在生产中造成危害之前发现错误的好方法。在部署前运行所有这些不同类型的测试有助于我晚上睡得更好。