编辑推荐: |
本文章主要从单元测试概念、单元测试的好处、单元测试背景、引入一些中间件遇到困难,给出的一些解决方案等五个方面进行介绍。
本文来自于葫芦APP知识中心,由火龙果软件Anna编辑、推荐。 |
|
概念
单元测试这个概念相信大家应该并不陌生。维基百科上有这样一段描述:在计算机编程中,单元测试(英文:Unit
Testing)又称为模块测试,是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。读起来并不难理解,但究竟什么才能算是软件设计的最小单位呢?在面向对象编程中,最小单元可以理解为方法,但是应当是只具有一种行为的方法。这里可以用一个现实中的例子来类比,把软件比作一架飞机,那最小单位可以认为是组成飞机的不可再分的一个零件,比方说一个螺丝钉,软件的单元测试可以认为是对零件的功能和完整性的检测。
单元测试的好处
如何理解单元测试在软件整体测试中的位置呢?这个问题,可以参考Mike Cohn的经典测试金字塔模型。如下图:
模型将软件的测试分为三层,从上到下依次是UI、服务和单元测试,我们不必纠结软件的测试是不是应该划分为这三层,因为随着时代的发展,仅仅划分这三层已经远远不够。但我们可以从这个经典模型中获取关于单元测试的一些必要信息:
软件的整体测试应该是分层的,单元测试处于所有类型测试的最底层,是最基础的。
需要编写的测试用例,越是底层,数量越多,单元测试是测试用例最多的一层。
这里仍然可以用飞机螺丝钉模型来理解这个模型,单元测试可以理解为飞机不可再分零件的测试,服务测试可以类比用零件组建了一个发动机,对发动机进行测试,UI可以理解为整个飞机在跑道上做起飞降落的测试。按照这个理解层级越往上,整体测试的聚合程度越高,测试的内容越来越接近真实的业务需求和实际场景,但是测试的复杂性更高,定位bug会更困难,修复bug的成本会更高,关于这点可以参考以下这个案例。
这是微软测试的在软件的不同测试阶段,解决一个bug平均的花费时间,可以看到在单元测试阶段解决一个bug的成本是最低的,后续阶段由于程序聚合程度的提高定位和修复bug的成本要高很多。
上面这张图描述了软件整个生命周期中bug的产生、定位以及修复成本的折线图,从图可以看出:
·一个软件约85%的bug是在编码阶段产生的
·单元测试可以检测编码阶段很大一部分bug
·单元测试阶段检测修复bug的成本是远远小于后续的集成程度更高的测试阶段的
综合以上可以得出结论,单元测试在整个软件生命周期中,如果切实得到履行,可以用最低的成本解决一大部分bug,是一件低投入高回报的事情。
下面这个案例同样也是一个例证:
这是《单元测试的艺术》一书中的一个案例。两个开发能力非常接近的团队开发相同的功能,进行单元测试的团队在编码阶段由于需要编写单元测试代码,花费了较多的时间,但是在后续过程整体交付时间要更优,由于进行了单元测试,最后客户发现的缺陷数也是远远小于不进行单元测试的团队,相信通过这个例子,可以更清楚的理解单元测试的必要性了。
其实除了以上的好处,单元测试也一定程度上驱动代码设计,因为写单元测试的过程其实也是对自己编写的程序代码的重新审查的过程,是一次再思考。当单元测试难以进行编写时,或许就需要我们重新思考业务代码的设计及编写了;通常设计良好的代码又是容易编写单元测试的,因此这也是一个相互促进的过程。
当然单元测试带来的好处肯定是包含以上但不局限于以上,所以如果在软件开发过程中具有编写单元测试的条件,应该切实推行,相信你会慢慢体会它带来的好处。
背景
通过前面的介绍,相信大家对单元测试的必要性有了一定理解,下面主要介绍微服务架构下单元测试如何进行。
近几年微服务大热,按业务划分的微服务单元独立部署,运行在独立的进程中,服务与服务之间没有任何耦合,有很好的扩展性和复用性;但是对单元测试带来了一些不便之处,相对于单体应用,可能业务实现由原来的本地方法调用变成了需要调用多个其他服务才能实现,如果再考虑例如性能、解耦等的设计,还需要引入一些中间件,比如kafka,redis等,这些都让我们的单元测试变的很困难。总结起来可以概括为以下几个方面:
微服务架构中调用链复杂:基于微服务架构,服务和服务之间存在大量调用,一个简单的方法中可能存在对远端服务的调用,远端服务可能又调用了其他服务,测试环境搭建困难。
数据库及缓存环境及测试数据构建:服务对于数据库的访问,需要保证数据库环境的可用,并且测试过程中需要根据实际业务在数据库或缓存中创建合适的测试数据,测试场景较多的情况下需要耗费大量的精力准备测试数据。
中间件相关方法测试:中间件的使用,如消息队列,kafka等,这类中间件相关方法测试要保证环境的可用性及相应的数据的可用性,测试环境搭建困难。
复杂对象构建及特殊方法:方法中涉及的复杂对象在测试时创建困难,例如session的创建;方法中调用的特殊方法,如static,final方法等的单元测试。
解决方案
针对上述情况,如果按照真实场景构建出外部依赖,会大大增加单元测试的工作量;单元测试的目的只是检验代码的流程以及结果正确与否,这些外部依赖是否存在我们并不关心,因此迫切需要一种技术手段来屏蔽代码中的外部依赖。这其实就是单元测试中mock的概念,mock的中文含义为虚假的,模拟的。mock意味着虚拟出上述难以构建的外部依赖,指定外部依赖的预期行为,调用待测试方法时外部依赖按照预期行为返回预期值,这样我们就可以只注重代码的流程和结果,进而让单元测试变的更轻量级和独立,简化单元测试的施行。
Java的mock框架有很多,这里我们根据自己项目的实际情况,技术选型为基于Mockito的PowerMock框架。选择这个框架的原因包含以下几个方面:
功能性: 框架功能上可以覆盖项目中所有单元测试的场景
社区活跃度: 社区足够活跃,项目一直有团队在维护和更新
开发友好度: 框架的api足够简洁,可读性高,便于开发
资料丰富性: 框架已被广泛使用,在使用过程中遇到问题可以很容易找到对应的解决方案
上图是基于我们项目的需求,功能上覆盖的单元测试的场景,PowerMock框架都能完美解决。
以上是关于单元测试及Mock框架技术选型的介绍,后续会对上述提到的场景进行demo的展示和介绍,敬请关注。 |