从零开始写单元测试( 三 )

单元测试常见问题下面聊一聊单元测试常见的一些问题 。
先写测试还是先写产品代码?都可以 。虽然有一种说法是TDD推荐的是先写测试,再写实现 。但很多刚开始写单元测试的同学并不习惯这种方式 。先写测试有一个好处,可以让你在设计代码的时候从业务视角去思考,而不是代码实现视角 。大家可以尝试先写测试再写实现,体会一下这种感觉 。

?
b站上有一个用TDD来实现计算斐波那契数的视频,是标准的TDD流程,感兴趣的同学可以去看一下,叫《TDD实战@计算斐波那契数》
?
写单元测试需要花费大量额外的时间?这个其实在文章开篇已经讨论过了 。写单元测试确实会花费更多的“写代码”的时间,但是总的来说,它可以缩短整个需求开发周期的时间 。所以写单元测试完全是一笔“划算的生意” 。
什么代码最需要单元测试?不自信的代码,逻辑复杂的代码,重要的代码 。比如工具类、三层架构的Service层、DDD的聚合根和领域服务等,这些都应该写足够的单元测试 。
入参对象构造太麻烦?构造一个合适的入参对象比较麻烦,尤其是有些对象有非常多的参数,如果每个测试都要去从头构造的话,会让测试代码变得非常臃肿,可读性变差 。这个时候可以使用工厂类来批量生产对象 。这个工厂类放在测试目录下,并不会对生产代码造成影响 。前面的例子里面,UserFactory就是一个User对象的工厂类 。
返回值为void测什么?返回值为void,说明方法没有出参,那方法内部必然有一些行为,它可能是「改变了内部属性的值」,也可能是「调用了某个外部类的方法」 。
如果是改变内部的某个值,那可以通过对象的get参数来断言 。这在使用DDD后的领域模型是一个问题,因为有可能本来产品代码不需要暴露出get方法的,但由于测试需要,暴露出了内部属性的get方法 。虽然使用反射也可以拿到内部属性的值,但没有太大必要 。权衡利弊,还是暴露领域模型的get方法好一点 。
如果是调用某个外部的方法,可以用verify来验证是否调用了某个方法,可以用capture验证调用其它方法的入参 。这样也可以验证产品代码是否如自己预期的设计在工作 。
static方法如何mock?static方法不好mock,需要用特殊的mock框架 。比如PowerMock、JMockit 。一般来说,Utils类的方法很多是static的,我们用得很多的时间类LocalDateTime,获取当前时间,也是static的 。这个时候需要用专门的mock框架来mock一下 。
多线程如何测试?多线程也不好测试 。如果程序简单,可以用「睡眠」或者CountDownLatch等多线程工具类来辅助测试,等所有线程跑完,再统一验证 。
如果程序相对复杂,需要使用专门的多线程测试框架,比如tempus-fugit、Thread Weaver、MultithreadedTC、以及OpenJDK的jcstress项目等 。
关于具体的框架如何使用,以后有时间可以写一篇常用的注解的介绍 。其实官方文档里面都有写,大家照着官网写几个例子就会了 。比较推荐的基础套餐是junit 5 + mockito + assertj 。关于static方法和多线程测试框架,大家有需要的时候再去了解也行 。
关于作者我是Yasin,一个有颜有料又有趣的程序员 。
微信公众号:编了个程
个人网站:https://yasinshaw.com




推荐阅读