企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
如果你认真观察大多数程序员如何分配他们的时间,就会发现,他们编写代码的时间仅占所有时间中很少的一部分。有些时间用来决定下一步干什么,有些时间花在设计上,但是,花费在调试上的时间是最多的。我敢肯定,每一位读者一定都记得自己花数小时调试代码的经历——而且常常是通宵达旦。每个程序员都能讲出一个为了修复一个bug花费了一整天(甚至更长时间)的故事。修复bug通常是比较快的,但找出bug所在却是一场噩梦。当修复一个bug时,常常会引起另一个bug,却在很久之后才会注意到它。那时,你又要花上大把时间去定位问题。 我走上“自测试代码”这条路,源于1992年OOPSLA大会上的一个演讲。有个人(我记得好像是Bedarra公司的Dave Thomas)提到:“类应该包含它们自己的测试代码。”这让我决定,将测试代码和产品代码一起放到代码库中。 当时,我正在迭代方式开发一个软件,因此,我尝试在每个迭代结束后把测试代码加上。当时我的软件项目很小,我们每周进行一次迭代。所以,运行测试变得相当简单——尽管非常简单,但也非常枯燥。因为每个测试都把测试结果输出到控制台中,我必须逐一检查它们。我是一个很懒的人,所以总是在当下努力工作,以免日后有更多的活儿。我意识到,其实完全不必自己盯着屏幕检验测试输出的信息是否正确,而是让计算机来帮我做检查。我需要做的就是把我所期望的输出放到测试代码中,然后做一个对比就行了。于是,我只要运行所有测试用例,假如一切都没问题,屏幕上就只出现一个“OK”。现在我的代码都能够“自测试”了。 从此,运行测试就像执行编译一样简单。于是,我每次编译时都会运行测试。不久之后,我注意到自己的开发效率大大提高。我意识到,这是因为我没有花太多时间去测试的缘故。如果我不小心引入一个可被现有测试捕捉到的bug,那么只要运行测试,它就会向我报告这个bug。由于代码原本是可以正常运行的,所以我知道这个bug必定是在前一次运行测试后修改代码引入的。由于我频繁地运行测试,每次测试都在不久之前,因此我知道bug的源头就是我刚刚写下的代码。因为代码量很少,我对它也记忆犹新,所以就能轻松找出bug。从前需要一小时甚至更多时间才能找到的bug,现在最多只要几分钟就找到了。之所以能够拥有如此强大的bug侦测能力,不仅仅是因为我的代码能够自测试,也得益于我频繁地运行它们。 > ![](https://box.kancloud.cn/9cf522e33e311401bf0d755d003df8ea_19x20.jpeg) 确保所有测试都完全自动化,让它们检查自己的测试结果。 注意到这一点后,我对测试的积极性更高了。我不再等待每次迭代结尾时再增加测试,而是只要写好一点功能,就立即添加它们。每天我都会添加一些新功能,同时也添加相应的测试。这样,我很少花超过几分钟的时间来追查回归错误。 从我最早的试验开始到现在为止,编写和组织自动化测试的工具已经有了长足的发展。1997年,Kent Beck从瑞士飞往亚特兰大去参加当年的OOPSLA会议,在飞机上他与Erich Gamma结对,把他为Smalltalk撰写的测试框架移植到了Java上。由此诞生的JUnit框架在测试领域影响力非凡,也在不同的编程语言中催生了很多类似的工具\[mf-xunit\]。 > ![](https://box.kancloud.cn/9cf522e33e311401bf0d755d003df8ea_19x20.jpeg) 一套测试就是一个强大的bug侦测器,能够大大缩减查找bug所需的时间。 我得承认,说服别人也这么做并不容易。编写测试程序,意味着要写很多额外的代码。除非你确实体会到这种方法是如何提升编程速度的,否则自测试似乎就没什么意义。很多人根本没学过如何编写测试程序,甚至根本没考虑过测试,这对于编写自测试也很不利。如果测试需要手动运行,那的确是令人烦闷。但是,如果测试可以自动运行,编写测试代码就会真的很有趣。 事实上,撰写测试代码的最好时机是在开始动手编码之前。当我需要添加特性时,我会先编写相应的测试代码。听起来离经叛道,其实不然。编写测试代码其实就是在问自己:为了添加这个功能,我需要实现些什么?编写测试代码还能帮我把注意力集中于接口而非实现(这永远是一件好事)。预先写好的测试代码也为我的工作安上一个明确的结束标志:一旦测试代码正常运行,工作就可以结束了。 Kent Beck将这种先写测试的习惯提炼成一门技艺,叫测试驱动开发(Test-Driven Development,TDD)\[mf-tdd\]。测试驱动开发的编程方式依赖于下面这个短循环:先编写一个(失败的)测试,编写代码使测试通过,然后进行重构以保证代码整洁。这个“测试、编码、重构”的循环应该在每个小时内都完成很多次。这种良好的节奏感可使编程工作以更加高效、有条不紊的方式开展。我就不在这里再做更深入的介绍,但我自己确实经常使用,也非常建议你试一试。 大道理先放在一边。尽管我相信每个人都可以从编写自测试代码中收益,但这并不是本书的重点。本书谈的是重构,而重构需要测试。如果你想重构,就必须编写测试。本章会带你入门,教你如何在JavaScript中编写简单的测试,但它不是一本专门讲测试的书,所以我不想讲得太细。但我发现,少许测试往往就足以带来惊人的收益。 和本书其他内容一样,我以示例来介绍测试手法。开发软件的时候,我一边写代码,一边写测试。但有时我也需要重构一些没有测试的代码。在重构之前,我得先改造这些代码,使其能够自测试才行。