答疑解惑 那些东西怎么测? 你好,我是郑晔!

到现在,《程序员的测试课》的正文内容我已经全部交付给你了。在专栏上线这段时间里,感谢你的一路相伴。有不少同学在专栏的问了一些非常典型的问题,引发了我的一些思考。所以,我准备了这期加餐,把我的这些延伸思考分享给你。

实战项目

这个专栏是我第一次用完整实战的方式给你演示如何做一个项目。秉承我写专栏一贯的特点,我写实战的重点是做事情的思路,而非具体的源码。

程序设计语言

不过,只要涉及具体的代码,就会有各种问题产生。最典型的问题就是,在实战中我采用了Java,有一些擅长其他语言的同学会问到我能不能提供其他语言的版本。

首先,我必须向这些同学说声抱歉,因为时间和能力有限,我没法提供各种程序设计语言的实现版本。这个ToDo 应用原本是《代码之丑》中的练习题,在那一讲里,同学们给出了很多不同的实现,有各种语言的版本,如果你有兴趣不妨去看看留言区里其他同学是怎么实现的。

当然,这个问题本身并不复杂,最好是你自己实现一遍,然后再对比我的实现过程,看看有哪些有差异的地方。编程这件事,讲道理远远不如动手实践来得感受更深刻。至于我写的代码本身,重要性没有那么强,只是一个参考。

之所以采用 Java 语言,一方面是因为它受众极广,另一方面更重要的是,Java 语言在工程上的能力非常好。无论是各种工具和框架还是工程实践上,Java 社区往往是走在整个行业的前列。

写这个专栏的时候,我已经尝试尽量降低理解的难度,把解决问题的过程和使用的工具尽量做到通用,尤其是前两讲的中用到的。即便你用的是其他程序设计语言,也可以找到对应的解决方案,比如命令行解析的框架、覆盖率检查的工具、构建工具等等。我们学习任何一门语言,要学的内容都不应局限于语言本身,还应该学习这个语言相关的生态。所以,如果你使用的是其它语言,却不知道我提到的这些东西,不妨借着这个机会,给自己补充一下。

不过,像 Spring 这种框架不是每个语言都有的,严格地说,Spring 已经超过了框架的范畴,它已经是一个完整的生态了。

关于程序设计语言,我一贯的建议就是多学几门,千万别局限在某一门语言里。所以,如果你擅长的是其它语言,可以借这个机会了解一下 Java 程序是怎么写的。

程序库

除了程序设计语言,还有一类问题是关于其它程序库的,也就是借着实战的项目做了一下延展,比如使用了某某程序库的程序该怎么测。

其实这个问题我在集成测试里已经说过了,首先要有一个单独的防腐层,将第三方代码隔离开,先保证逻辑的正确,再来看第三方程序库。与第三方程序库的集成,能测就尽量测,不能测就只能靠更高层的测试来覆盖了。

其实,现在很多的程序库或是中间件都有自己对于测试的支持,一种简单的方案就是用要集成的东西加上 Mock 作为关键字去搜索,比如 Mock Kafka、Mock Dubbo 等,如果你运气好的话,会得到相应的答案,剩下就是阅读文档的工作了。

有一些地方是我在写专栏的过程中没注意到的,可能会给你造成困扰。比如我用到 Lombok 这个程序库,它会替我们生成一些代码,节省编码的工作量,没用过 Lombok 的同学看到这样的代码可能会有些懵。

说到 Lombok,我再多补充一点。在测试的过程中,有一些典型的样板代码是让人很难受的,比如 getter、equals、hashCode、toString,这种代码不写,行为不对;写,就会有测试覆盖率的问题。给这些方法写测试是非常麻烦的一件事,这里的麻烦就是单纯的麻烦,实现细节是标准的,写的测试也几乎长一个模样。

所以在实际的项目中,我现在常常会采用 Lombok 这个程序库,它会替我生成这些标准的方法,就像下面这样。 @EqualsAndHashCode @Getter @ToString @NoArgsConstructor(access = AccessLevel.PRIVATE) public class TodoItem { … }

我们只要在相应的位置加上 Annotation,在编译的过程中,Lombok 就会替我们生成相应的代码。所以,虽然我没有一行一行地把代码写出来,但我们依然可以在程序中使用相应的方法。这就是一个好的程序库价值所在,它可以极大地简化代码的编写。

这些方法是生成的,而不是我们手工编写的,所以,我们也没有必要去检查这些方法是否正确,在执行测试覆盖率检查时,我们可以忽略掉这些方法。Lombok 和 Jacoco 之间已经有了这种默契,所以,你会在 Lombok 的配置文件 lombok.config 看到下面这样一句。 lombok.addLombokGeneratedAnnotation = true

它会给生成的方法加上一个 Annotation,Jacoco 看到了这个 Annotation 就会知道它是一个生成的方法,进而在测试覆盖的统计中忽略掉它。

那些东西怎么测?

还有一类的问题相对来说就很宽泛了,比如有人问怎么测试大数据模型的正确性。要回答这个问题,我们要先看看自动化测试究竟能解决什么问题。

首先,我们需要明确一点,程序员在日常开发中写测试除了我们之前说过的种种原因之外,还有一点就是为我们的开发保驾护航,这些测试要能够时时刻刻起到警戒线的作用。这种测试写出来要常常跑的,而不是束之高阁。所以,它还有一个好搭档就是持续集成。多说一句,只有做好了自动化测试,持续集成才能发挥出它最大的价值。

有了前面的理解,我们再来看哪些东西能起到这样的作用:那些能够让人形成稳定预期的东西。我们前面写的各种测试,都是能够有稳定预期的,这也是我们说测试一定要有断言的重要原因。

回过头来看前面的问题。大数据模型这种东西,你能有稳定的预期吗?你或许会说,我希望得到一个好的结果,然而,这样说法一点都不具体,什么叫好的结果呢?这就像我希望身体健康一样,这只是美好的愿望,它并不会因为我们“希望”了,它就能够实现出来。对于这种你连用语言都无法描述的东西,就不要指望用测试描述了。

其实,你真正关心的不是模型的正确性,而且它的效用,也就是说,它到底有什么用。

效用这种东西,我们没有办法对它形成稳定的预期。举个例子,你说 100 万多吗?对大部分普通人来说,这已经是很大一笔钱了,但对于世界首富来说,这笔钱就不算多了。所以,同样是得到 100 万,对不同的人来说,效用完全不同。即便是对同一个人,当他人生处于不同的阶段,这笔钱对他来说意义也是完全不同的。

效用甚至都很难达成共识,那就更别说预期了,所以,大数据模型不是靠自动化测试能解决的。类似的东西还有很多,比如用户界面好不好看、软件的体验好不好,这些东西都属于效用。

那效用的东西就不能测了吗?也能,只不过,不是自动化测试。严格地说,效用的好坏要依赖于反馈。数据模型的有效性要靠业务来反馈,软件的好用还是好看要靠用户来反馈。

在软件开发的实践中有一种实践叫用户测试,简单说就是让用户参与到软件开发的过程中。还有一种实践叫 A/B 测试,就是把不同的东西给不同的用户看,把用户行为当作下一步决策的基础。你会发现,无论是哪种做法都不是靠简单的自动化测试能够覆盖的,因为这个过程中要有人的参与,人会根据反馈回来的信息进行判断。

既然这些依赖于人的测试不好自动化,是不是技术类的就都可以了呢?比如有人问,性能测试能不能放到程序员的测试里?

性能测试是一种可以用技术覆盖的测试,现在有很多程序库支持我们进行性能测试,比如 JMHGatling 等等。大到系统,小到单元,都可以使用性能测试进行覆盖。也正是这些程序库的存在,让我们可以通过代码来写性能测试,也让性能测试用测试框架覆盖成为可能。

不过,有一点我必须提醒你,性能测试最难处理的是断言怎么写。你或许会说,不就是和其他测试一样写吗?不尽然。

有很多团队开发用的是 Windows 或者是 Mac,而实际的项目是在 Linux 运行。我们知道,性能这个东西在不同的机器上跑出来的结果差异很大。即便是同一台机器,因为负载的差异,跑出来的结果也会有很大差异。所以,我们很难写出一个放在所有环境上都可用的断言。

从实践的角度上看,我不会把性能测试归到程序员的测试范畴。当然,如果你愿意用单元测试框架去写一个自动化性能测试,也是可以的,但请把它同其他测试隔离开来,其他的测试要在持续集成的全过程中运行,而这种自动化性能测试只在单一的机器上运行,比如持续集成服务器,这样做至少可以保证前后运行的结果不会因为机器原因产生很大的差异。

测试固然有用,但它不是万能的。作为程序员,我们只有分辨清楚自己面对的究竟是什么问题,才能使用相应的工具去解决问题。

思考题

关于程序员的测试,你还有哪些问题或者哪些经验可以分享吗?欢迎在留言区分享你的思考。

参考资料

https://learn.lianglianglee.com/%e4%b8%93%e6%a0%8f/%e7%a8%8b%e5%ba%8f%e5%91%98%e7%9a%84%e6%b5%8b%e8%af%95%e8%af%be/%e7%ad%94%e7%96%91%e8%a7%a3%e6%83%91%20%e9%82%a3%e4%ba%9b%e4%b8%9c%e8%a5%bf%e6%80%8e%e4%b9%88%e6%b5%8b%ef%bc%9f.md