序
我教计算机科学与软件工程相关课程已经30年了,感觉很少有其他技术能像自动化单元测试(automated unit testing)这样大幅影响我的教学(与研究)工作,在2001年短暂进入业界的那段时间,这种感觉尤其强烈。自动化单元测试是一套通用的方法,将这种方法与测试驱动开发(Test-Driven Development, TDD)理念相结合,可以令其更为明确地应用于多个领域。
我还记得自己当初是怎么突然之间就明白了TDD,其实那个时候并不是专门为了去理解TDD,而是在讲授其他知识的时候想到了这个概念。那时我在讲面向对象的开发,我把Martin Fowler写的《UML精粹(第3版)》用作UML方面的参考书。Martin在那本书中讨论了迭代式开发流程的三个关键做法,也就是自动化回归测试(automated regression test)、重构(refactoring)与持续集成(Continuous Integration, CI)。我很认同这三种做法,而且很乐意鼓励学生多写一些代码,去测试他们打算编写的那些正式代码,这样就可以在一套用颜色来表示测试结果的界面之中立刻看到那些正式代码是否能够通过测试。
还有一个让我顿悟的时刻,出现在大约10年之后,也就是2012年左右,那时我开始在Software Engineering Radio上面听关于软件架构的播客。我仔细阅读了播客里面提到的某些参考资料,当时突然看到Robert C. Martin[也就是Uncle Bob(Bob大叔)]写的Agile Software Development: Principles, Patterns, and Practices(《敏捷软件开发:原则、模式与实践》),里面有一小节(也就是4.2.2节)叫作“Serendipitous Architecture”(意外获得的架构/偶得的架构)专门讲了一个道理:如果你一开始就注意编写那种可以接受测试的代码,那么项目的架构就自然会变得比较合理,而且更加容易维护。
这两次经历让我体会到了自动化测试是如何把流程与架构,以及功能性需求与非功能性需求结合起来的:这种测试让我们能够更加明确地了解自己写出来的代码在多大程度上满足功能性的需求,同时,为了能够在正式代码中做这样的测试,我们必须在编写这些代码的时候专门留意如何让代码易于测试。从这一点看,可测试性(testability)可以说是最为重要的非功能性需求。
又是差不多10年之后,也就是2021年夏天,Saleem Siddiqui联系到我,让我看看他写的这本书。我忽然想起,明年就是Saleem跟我学习那三门研究生课程的25周年纪念了。他现在是一位成功的技术专家,而且跟Martin Fowler一样,也成为Thought-works的一员,并且还写了书。看到这些,我深感欣慰。Saleem让我给他的书写序,我觉得很荣幸,而且我急着想要知道他对TDD的看法。
Saleem这本书里最让我高兴的地方是他用一种既直观又有条理的手法,通过大家所熟悉的日常事例来讲解TDD的流程。无论你采用哪种语言编写程序,“红-绿-重构”(red-green-refactor)这三个环节[1],都可以反复执行。作者是拿金融货币领域的需求来举例的,这样的示例清晰易懂,而且让读者能够逐渐面对更为复杂的挑战,从而了解如何写出健壮的代码,如何在各种细节问题上进行权衡,这样一来,大家就很愿意更为深入地探索了。在本书最后,作者提出从profile(形象)、purpose(目标)与process(过程)这三个维度(3P原则)来评审代码,这三方面正好涵盖了书中所讲的各种理念。
本书采用三种流行的编程语言来讲解,这三种语言在设计上面可以互相补充,其中,JavaScript与Python已经是业界极为常用的语言了,而Go语言的人气也在迅速攀升。通过这三门语言,Saleem有力地证明了TDD是一种适用范围很广的开发方式。此外,他还多次提醒读者注意编程语言的设计与刚才说的3P原则之间的关系。
我最希望看到的是许多新一代的软件开发者在读完本书之后,能够坚定地采用测试驱动开发(TDD)这样一种方式来使用Go、Java与Python等流行语言,这样能让TDD传播得更为广泛。著名的萨克斯爵士乐手Cannonball Adderley在演奏现场向纽约的观众解释什么是hipness时说过,这不单单是一种想法,而是一种活法(It's not a state of mind, it's a fact of life),我想借用这句话来描述TDD。
Konstantin Läufer
芝加哥洛约拉大学(Loyola University Chicago)
计算机科学教授
2021年9月于美国伊利诺伊州芝加哥
[1] 先根据需求编写测试代码,此时由于缺乏能够满足需求的正式代码,所以测试无法通过(红),然后编写能够满足需求的正式代码,让测试得以通过(绿),最后把代码调整得更加完善(重构)。