十多年前刚从学校毕业,去了一家创业公司,在这家公司做了不少软件,可以说完成了职业发展的启蒙。
2012 年左右,互联网发展方兴未艾,开发速度和快速发布是软件开发中的第一优先级。
测试?
哪有什么测试,自己点两下没报错就行了,反正上线后用户就是公司的测试。“互联网公司不需要测试人员,用户就是测试”这是老板的原话。
没有质量部门,也没有测试人员,三五个程序员几台笔记本,就是一家互联网公司,这是当时的常态。使用的技术一般也是类似于 Cake PHP、Ruby on Rails 之类的快速开发框架。
这样的产品的质量可想而知。甚至有时候不能当作产品,只能算一种高级的玩具,使用稍微复杂的功能就能遇到大量报错。在很多年后,得知 Martin fowler 还给这类项目取了一个名字——献祭项目。
即便如此,这些项目还是被部署到生产环境中,也有真实的用户在用。我们为景区开发了一套电子票系统,游客通过二维码和身份证就能进入园区。第一个服务器版本是我一个人花了不到一个月完成的,居然有数几十个页面。
和闸机客户端联调了几天就上线了,就这样被部署到景区。 结果大家都能猜到,我成功的当上了“救火队员”。
上班地点从公司变成了客户现场,当游客因为服务器原因无法进入园区时,我就需要调查问题,并快速修复,然后再上线。
每一次大的版本迭代,都需要在客户现场蹲上那么几天。
爱上单元测试
互联网发展初期是个神奇的时代,人们倡导没什么事情程序员是不能干的。实际上也是这样的,很多人程序员兼任项目经理、产品经理甚至 UI 设计师。当然测试这件事情也不例外,为了不天天跑客户现场,我开始自学软件测试。
这期间还发生了一些有趣的故事。我跑到四川省软件测试中心实习,才发现他们的“测试”工作不过是运行软件看看其功能是否和软件著作说明书是否一致,然后颁发软件著作权证书。
这和我想象中的软件测试相差甚远。
我买了一些测试的书籍。其中大部分书像教科书,而有一本书令我印像深刻,这本书就是《大话软件测试》,其中提到了一些单元测试的内容。
但是在当时,单元测试的资料还非常少,大部分测试框架都是 Java 的 JUnit。虽然已经有了 PHPUnit,使用起来也不是很方便。
即便如此,单元测试也大大解决了我们软件质量的问题,一些低级的错误的开发过程中就被避免了。于是我对单元测试非常感兴趣,我甚至创建了 PHPUnit 百度百科的词条,翻译了 PHPUnit 的官网和文档。
对 TDD 感到困惑
随着前端工程化的发展,我开始学习 AngularJS。
我买了一本鱼书《AngularJS开发下一代Web应用》,这本书的叙述风格为单元测试先行,然后再对其实现。
在这本书中,我首次知道测试驱动开发(TDD)的概念,不过对于 AngularJS 来说,为前端代码编写单元测试和服务器代码尤其是 Java 差异十分巨大。
《AngularJS开发下一代Web应用》并不是一本入门 TDD 很好的书,前端的测试环境非常复杂,夹杂非常多的概念和框架,例如 karma、Jasmine、Mocha 等。
我陷入了非常困惑的状态。一方面,很多讲授敏捷的书都在强调和推崇 TDD,但是自己实践起来体验非常糟糕,因为在编写代码之前不知道如何编写测试,并且花费太多的时间在编写测试上。
后来我才知道,Java 程序员在工程中使用 TDD 要简单的多,也不像很多敏捷的书那样,一定需要按照“最佳实践”的方式 TDD。
很多 TDD 社群甚至变得非常形式化,当你请教一些 TDD 问题时,他们会觉得是你的 TDD 姿势不对。
后来我放弃了 TDD 中很多实践,例如 Baby Step、PingPong Pair 等。 TDD 更多的是一种思维转变,也就是先设定编程目标,再编写代码就能看作 TDD。
坚持 TDD 的方法是,找到一种舒适的姿势来实践 TDD,而无需关心 TDD 布道者教授的繁琐仪式。
一些 TDD 技巧很有用,但是前提是先习惯 TDD,不是吗?
因为测试加入 Thoughtworks 公司
我希望去一家更加专业的软件公司,一直"救火"很累并且非常有挫败感。在国内,践行单元测试的公司极少,于是我尝试给外企投简历,因为好像只有外企才编写单元测试,这刚好和我想要做出合格软件的目标匹配(现在想来,因为这种追求找工作显得有点天真)。
于是自然地去了 Thoughtworks,那时候还叫 ThoughtWorks。 说实话在这家公司做的软件不算高端,但重要的是,他是一家很严肃的商业公司。
一般来说,为客户定制软件的公司属于外包,而外包在大多数语境下,属于低廉、劣质的体现,但这家公司恰恰相反。
很多程序员都有类似的经历,做的软件都属于"玩具",做的快,但一用就是一堆问题。 在 Thoughtworks 工作期间,才开始体验到什么叫"软件产品",做的软件都被客户日常使用,且基本上没啥问题,刚开始确实有一种成就感。
但也颠覆了我很多认知:开发者不仅需要编写单元测试,还需要关心集成测试、E2E 测试。测试人员也需要编写代码,甚至有些项目测试人员接近了开发人员的数量。
编写编写单元测试和集成测试(API)测试往往占用了大部分工作时间,但免去了每天处理生产问题的烦恼,也不用无休无止的修复线上数据。
毕竟,一个不能用或到处都是问题的功能,即使做的再快也没啥用。
编写《Java 研发自测》
在 Thoughtworks 工作期间,大部分时间使用的编程语言是 Java,相比 PHP,Java 确实是一种更加严谨的编程语言。
Java 生态中,有非常成熟的测试框架和库来完成各种各样的需求。 例如,测试框架、模拟第三方 API、内嵌数据库和 Redis,以及统计测试覆盖率等。
当然这些工具和框架其它编程语言也有,不过没有这么成熟和完善。
由于团队新人的加入,我开始把在项目中编写单元测试、API 测试的内容整理成文档供他们学习使用。这些文档在同事间传播,并提出了非常多有建设性的改进意见,让其更加实用。
后来公司市场同事张凯峰老师找到我,觉得这些内容挺有价值,问我是否考虑出版(我本来只是希望当作专栏或者电子书,出版也许过于专业)。
在和出版社编辑杨绣国老师的沟通下,我们将其定位为:"开发人员对软件进行自测的书。"。我觉得这个定位和主题很不错,因此添加了日常项目中关于质量的内容,例如对代码扫描、Code Review、并发检测、测试工程化、TDD 等内容。随后魏兆玉老师加入,让其更加丰富。
这些内容的加入,慢慢形成了《Java 研发自测》的雏形,在编辑老师长达两年的幸苦编辑下,最终得以出版。
希望这本书得到众多"救火队长"的喜欢。
不久前,有一位阅读过研发自测相关内容的朋友告诉我,虽然这是一个非常冷门的领域,但是对他们很有帮助,编写单元测试后,一次就和前端联调成功,非常有成就感。
小结
虽然这本书叫做《Java 研发自测》,但研发自测并不是一个新的概念。在海外,有个类似的概念叫做"测试左移",意思是测试需要尽可能发生在软件开发周期的前面,问题发现的越早,修复成本更低,其实长期来看,更加节省成本。
另外,好的现象是,国内的软件公司也越来越重视单元测试,或者说研发自测了。这可能和互联网发展的成熟有关系,人们从繁复的生产问题中发现把测试做"到位",可能更加节省成本,同时用户体验也越好。
最后附上京东的购买链接:https://u.jd.com/R8ZlYBw
完整的示例代码也已经放到了 GitHub:https://github.com/java-self-testing/java-self-testing-example
可以通过 Issue 提交相关问题,以便在下个版本中即使修复。
