上一篇文章,我讲到了微服务架构下的测试策略和质量保障体系,今天我来讲讲测试策略中的最底层测试——单元测试。
单元测试是一种白盒测试技术,通常由开发人员在编码阶段完成,目的是验证软件代码中的每个单元(方法或类等)是否符合预期,即尽早在尽量小的范围内暴露问题。
我们都知道,问题发现得越早,修复的代价越小。毫无疑问,在开发阶段进行正确的单元测试可以极大地节省时间和金钱。如果跳过单元测试,会导致在后续更高级别的测试阶段产生更高的缺陷修复成本。
如图,假如有一个只包含两个单元 A 和 B 的程序,且只执行端到端测试,如果在测试过程中发现了缺陷,则可能有如下多种原因:
由此可见,忽略单元测试会导致后续发现缺陷时,要花费较高的成本来确认缺陷原因。
单元测试除了能够在较早阶段识别软件中的错误,它还有如下价值。
既然单元测试由开发人员来设计和执行,那作为测试人员是不是就不需要学习这门技术了?不知道你是怎样看待这个想法的,我的观点是:
就像之前课程所说:微服务中最大的复杂性不在于服务本身,而在于微服务之间的交互方式,服务与服务之间常常互相调用以实现更多更复杂的功能。
举个例子,我们需要测试的是订单类(Order)中的获取总价方法(getTotalPrice()),而在该方法中除了自有的一些代码逻辑外,通常需要去调用其他类的方法。比如这里调用的是用户类(User)的优惠等级方法(reductionLevel ())和商品类(Goods)中的商品价格方法(getUnitPrice())。很显然,优惠等级方法或商品价格方法,只要一方有错误,就会导致订单类获取总价方法的测试失败。基于这种情况,可以有两种单元测试类型。
如图,测试订单类的获取总价方法(Order.getTotalPrice())时会真实调用用户类的优惠等级方法(User.reductionLevel())和商品类的商品单价方法(Goods.getUnitPrice())。将被测试单元视为黑盒子,直接对其进行测试,这种单元测试称之为社交型单元测试(Sociable Unit Testing)。
如图,如果测试订单类的获取总价方法(Order.getTotalPrice())时,使用测试替身 (test doubles) 技术来替代用户类的优惠等级方法(User.reductionLevel())和商品类的商品单价方法(Goods.getUnitPrice())的效果。对象及其依赖项之间的交互和协作被测试替身代替,这种单元测试称之为孤立型单元测试(Solitary Unit Testing)。
另外,上述提到的测试替身是一种在测试中使用对象代替实际对象的技术,常用的技术如下。
根据被测单元是否与其交互者隔离,会产生以上两种单元测试类型,这两种类型的单元测试在微服务测试中都起着重要作用,它们用来解决不同的测试问题。
由上图可知,在微服务架构中,不同组成使用的单元测试类型不同:
特别注意:当微服务的(网关+仓库+资源+服务层)与(域逻辑)之比相对较大时,单元测试可能收益不大。常见的情况有小型服务或某些几乎只包含了网关+仓库+资源+服务层等内容的服务,例如适配服务等。
在实际项目过程当中,应该怎样开展单元测试呢?通常来说,可以通过如下四个步骤来进行。
虽然单元测试很重要,但并不是所有代码都需要进行单元测试,可以重点关注核心模块代码或底层代码,如重要的业务逻辑代码或通用组件类等。
单元测试中的技术框架通常包括单元测试框架、Mock 代码框架、断言等。
关于它们的优劣网络上已有非常多的文章,这里不再赘述。综合来看,个人比较推荐使用Junit+Mockito+assertJ,我建议你根据自己的需求选型。
只单纯地看单元测试的执行通过率还比较单一,为了更全面地看到测试的覆盖情况,可以借助代码覆盖率工具和技术。在 Java 语言里,常用覆盖率工具有 Jacoco、Emma 和 Cobertura,个人推荐使用 Jacoco。
接入持续集成工具是为了形成工具链,将单元测试、代码覆盖率统计集成在一起,使得代码有提交时便自动触发单元测试用例的执行,并伴随有代码覆盖率的统计,最后可以看到单元测试报告的数据(用例通过情况和代码层面各个维度的覆盖数据)。接着可以判断是否需要修改代码,这便形成了一个代码质量的反馈环,如下图所示。
后续的文章还会讲解到代码覆盖率工具和持续集成工具。
了解了如何开展单元测试,那么如何做到最好呢?我们都知道,代码产生错误无非是对一个业务逻辑或代码逻辑没有实现、实现不充分、实现错误或过分实现,所以无论是拆解业务逻辑还是拆解逻辑控制时都要做到 MECE 原则(全称 Mutually Exclusive Collectively Exhaustive,中文意思是“相互独立,完全穷尽”,即日常沟通中常说的“不重不漏”)。
“不重不漏”说起来容易做起来难,为了努力做到它,写出好的单元测试,可以遵循如下具体的实践规范。
上述规范和实践经验比较多,可能会因为落地难度和成本而使开发人员望而却步,事实上可以采取“小步快跑”的方式,逐次提升不同方面的要求,拉长落地的战线。
本节课内容讲解了单元测试的定义:它是一种软件测试方法,目的是验证软件代码中的每个单元(方法或类等)是否符合预期,即尽早在尽量小的范围内暴露错误。
接着讲解了微服务架构下常见的交互场景,测试方式和对象的不同会出现社交型单元测试和孤立型单元测试两种单元测试类型。
然后讲解了实际如何开展单元测试,先确定要测试的代码范围,再引入单测框架、mock 框架、断言类型、代码覆盖率工具和持续集成工具,使代码提交过程形成一个有效的单元测试质量反馈环。紧接着我又给出了一系列的最佳实践或规范,包括类和方法的命名规范、目录规范、数据要求、验证结果要求、运行速度、质量卡点等,相信这些内容可以帮助你更好地设计和实现单元测试。
你所负责的项目或服务,是否运行过单元测试呢?如果有,欢迎在留言区评论,说说单元测试的落地情况是怎样的。同时欢迎你能把这篇文章分享给你的同学、朋友和同事,大家一起交流。
相关链接 https://www.martinfowler.com/articles/microservice-testing/#testing-unit-introduction 单元测试框架 TestNG官网: https://testng.org/doc/ TestNG教程: https://www.yiibai.com/testng/ Junit官网: https://junit.org/junit5/ Mock代码框架 Mockito: https://site.mockito.org/ jMock: http://jmock.org/ Easymock: http://www.easymock.org/ Powermock: https://github.com/powermock/powermock Mock框架对比: https://stackoverflow.com/questions/22697/whats-the-best-mock-framework-for-java 断言 Hamcrest: http://hamcrest.org/JavaHamcrest/ assertJ: https://joel-costigliola.github.io/assertj/assertj-core.html 覆盖率工具 Jacoco: https://www.jacoco.org/jacoco/trunk/index.html Emma: http://emma.sourceforge.net/ Cobertura: https://cobertura.github.io/cobertura/
© 2019 - 2023 Liangliang Lee. Powered by gin and hexo-theme-book.