当前位置: 首页 > 科技观察

Mock框架的三轮迭代,让你的单元测试更高效

时间:2023-03-17 00:59:55 科技观察

如何定义单元对于单元测试中的一个单元,不同的人有不同的看法:可以理解为一个方法,一个完整的接口实现,或者它可以理解为一个完整的功能模块或者多个功能模块的耦合。根据以往的单元测试经验,在设计单元测试用例时,在方法层面进行单元测试时,重点是方法的底层逻辑;当以模块为目标时,它以实际的业务逻辑实现为目标;当针对集成模块进行测试时,一般称为集成测试。不管是单元测试还是集成测试,都可以理解为单元测试。因为它们本质上是一种测试方法或接口的形式,只是处于不同的阶段。1.谁来写集成测试?在我们实际工作中,开发人员在提交代码之前,会设计一些“冒烟测试”级别的集成测试用例。整个功能开发完成后,测试人员根据业务需求和设计好的测试用例,对整体集成测试用例进行编写、执行、失败案例分析、代码调试和问题代码定位。2.集成测试用例业务相关的测试主要通过spring-test进行。基本的测试结构是先定义一个基类来初始化被测类。测试基类定义结构如下:@RunWith(SpringJUnit4ClassRunner.class)ContextConfiguration(locations={"classpath:./spring/applicationContext.xml"})@DirtiesContext(classMode=DirtiesContext.ClassMode.AFTER_CLASS)publicclassBaseSpringJunitTest{@AutowiredprotectedBusinessRelatedServiceImplbusinessRelatedService;}Business相关测试类定义如下格式:publicclassBusinessRelatedServiceImplDomainTestextendsBaseSpringJunitTest{@TestpublicvoidtestScenario1(){newThread(newDOSAutoTest("testScenario1")).start();Thread.sleep(1000*60*1);StringrequestJson=""//测试输入参数;RequestPojorequest=(RequestPojo)JSONUtils.jsonToBean(requestJson,RequestPojo.class);ResponsePojoresponse=businessRelatedService.businessRelatedMethod(ResponsePojo);//业务相关断言区}}3.如何解决下游系统在流程中对businessRelatedMethod方法的依赖处理业务逻辑需要调用下游JSF(京东服务框架,完全自研的高性能RPC服务框架)提供的订单接口(OrderverExportService),根据订单获取订单的详细信息输入参数中的数字(ResultPojogetOrderInfoById(longorderId))。那么如何获取下游JSF接口返回的正确数据就成了一个比较重要的问题。如果处于功能测试或联调测试阶段,可由下游测试人员提供数据。但通信和测试成本高,无法满足业务快速上线和变更的要求,尤其是在集成测试阶段,因为下游数据对上游是不可控的。这样一来,mocking下游数据就显得尤为紧迫和重要。4、Mock框架的选择在整个Java生态中,支持mock的开源框架还是有很多的,比如常用的开源框架有mockito、powermock、easymock、jmockit等。这些框架在mocking方面都具有比较强大的功能和广泛的用途。但是这些框架都有一个共同的缺点,就是需要或多或少的编码工作来模拟返回数据所需的接口。在设计mock框架时,我们考虑到编写单元测试的人员或者研发人员应该尽量少写或者不写代码,以获取不同业务场景需要的测试数据。最新版本的MockFramework这个版本的mock框架的总体思路是:结合JSF的特点,重写下游接口的所有方法,然后将实现下游接口的应用程序部署到测试环境,并发布一个与真正的下游接口不同的服务,接口调用时,通过不同的JSF接口别名来区分。Mock的数据存放在数据库中。框架类调用关系:Mock接口的具体实现:publicclassOrderverExportServiceImpextendsOrderverExportServiceAdapter{@ResourceprivateOrderverMapperorderverMapper;@OverridepublicResultPojogetOrderInfoById(longorderId){OrderverPojoorderverMock=orderverMapper.getOrderId(newLong(orderId).toStringd());ResultPojoresult1(newLongtoLong(ingorderI))));ResultPojoresult=1);result.setFiled1(0);result.setFiled2(null);result.setFiled3(null);result.setResult(true);...//模拟所需数据result.setReturnObject(orderver);returnresult;}}Mock服务发布后的效果:在集成测试阶段,只需要修改接口的JSF别名即可实现接口的mock调用。alias=orderver_mock这个框架的优缺点优点:设计集成测试用例时,不需要写代码,只需要维护测试场景需要的返回数据;该框架不仅可以用在集成测试中,在下游接口不变的前提下,也可以用在后续的系统测试和联调测试阶段。缺点:mock服务的发布依赖服务器和数据库。当依赖的服务器或数据库崩溃时,不使用mock服务;框架的维护成本相对较高。当下游依赖接口较多时,所有服务包含的所有方法都需要重写;当下游接口定义发生变化时,比如增加接口方法,mock服务需要重新覆盖新增加的方法,需要重新打包部署;当下游接口方法的数据结构发生变化时,存储数据的数据表的结构需要做相应的调整。对于业务变化较快的系统,这种变化的频率还是很高的。Mock框架第二版为了解决上述mock框架依赖服务器和数据库的问题,我们进行了第二次尝试。将mock框架设计成jar包提供给程序调用。在下游接口的实现上,第二个版本与第一个版本没有变化。同时,业务数据不放在数据库中,而是放在文件中。变化点是在接口调用上需要将对应的jsf:comsumer节点替换为对应的实际mock实现类。Mock接口的实现:@Service("orderverExportService")publicclassOrderverExportServiceMockextendsOrderverExportServiceAdapter{@OverridepublicResultPojogetOrderInfoById(longorderId){ResultPojoresult=newResultPojo();result.setFiled1(null);result.setFiled1(0);result.setFiled2(null);3result.setFiled(null);result.setResult(true);...//Mock所需数据result.setReturnObject(orderver);returnresult;}}Mock接口调用配置:这个框架的优缺点:设计集成测试用例时,不需要写代码,只需要维护测试场景需要的返回数据;与第一个版本相比,这个版本的执行效率有了很大的提升,因为mock类的加载是本地的Spring配置文件,数据的加载也是本地文件;无需依赖服务器部署和数据库依赖的缺点:框架的维护成本比较高。当下游依赖的接口较多时,需要重写服务中包含的所有方法;当下游接口定义发生变化,比如增加了接口方法,mock服务需要重新覆盖新增加的方法,需要重新打包上传到maven仓库;当下游接口方法的数据结构发生变化时,对于业务变化较快的系统来说,这种变化的频率还是很高的。Mock框架第三版随着需要mocking的接口越来越多,上述两个mock框架实现的缺点也越来越突出。这个框架可以说从根本上解决了上述框架实现的问题。因为框架充分利用了JDK的动态代理、反射机制和JSF提供的高级特性来实现我们的mock框架。无需执行更多特定于接口的编码任务即可完成框架维护任务。测试人员只需要专注于测试数据的准备。框架整体调用时序图:框架核心类图:DOSAutoTest类用于启动和发布JSFmock接口,JSFMock通过动态代理实现下游接口的mock功能,并根据测试场景获取相应的mock数据。其中mock数据以json格式存放在mock框架项目工程的指定目录下。本框架解决的问题:省去使用jmockit、mockito、powermock等第三方mock框架时在单元测试或集成测试类中编写mock代码的麻烦;框架在模拟数据返回时,完全模拟了接口之间的调用关系;测试人员或研发人员使用框架mock数据时,无需额外代码即可返回mock数据;模拟下游数据返回时,发布的mock接口会在调用完成后自行销毁,无需额外的服务器进行部署和维护。在进行接口mocking时,无需在mock框架中添加相关的接口maven依赖。单元测试部署方法1.谁来写单元测试?谁应该编写单元测试?关于这个问题,大家在网上会发现不同的说法:一种观点是,谁写代码,谁就自己写单元测试。当然,在一些结对编程中,也有相互编写的,只是在这个过程中,是两个人共同完成代码。不违背谁写代码谁写单元测试的原则。另一种观点认为,单元测试应该由其他开发人员或测试人员编写。道理大概可以这么理解,对于非coder来说,在设计单元测试用例的时候,对应的就是一个黑盒子。在这种情况下,设计的用例具有更高的覆盖度。2.单元测试行业现状如果R&D负责写单元测试,很多时候R&D人员并不写单元测试。开发人员不写单元测试的原因其实还是比较容易理解的,因为写单元测试用例太费时间了。有时研发经理或项目业务方认为单元测试用例会拖慢项目的整体进度。有时甚至整个公司都没有认识到在单元测试上花费大量时间是合理的,尤其是在项目周期紧、业务变化大的项目中。因为单元测试确实在一定程度上增加了开发人员的编码量,同时也增加了代码的维护成本。如果测试负责写单元测试,目前的现状是测试人员需要时间去理解代码,写单元测试的时间会变长。代码修改后,在项目的测试压力下,部分测试人员选择不再维护单元测试,而是选择快速完成传统的手工测试。3、单元测试用例的自动生成人工编写测试用例的成本增加,所以我们考虑是否可以通过自动生成来实现单元测试?EvoSuite是谢菲尔德大学与其他大学联合开发的自动生成测试用例的开源工具。Set,生成的测试用例都符合Junit标准,可以直接在Junit中运行。对于非业务相关的模块,在单元测试实践中,可以直接使用上述工具自动生成单元测试代码。虽然该工具只是辅助测试,不能完全替代人工,测试用例正确与否需要人工判断,但是使用该自动化测试工具可以在保证代码覆盖率的前提下,大大提高测试人员的开发效率.下面详细介绍如何使用该工具生成单元测试用例,以及如何检查单元测试用例的正确性。EvoSuite为Maven项目提供了一个插件。插件的具体配置如下:org.evosuite.pluginsevosuite-maven-plugin${evosuiteVersion}prepareprocess-test-classes除了配置上面的插件,maven还需要做如下配置:>testorg.apache.maven.pluginsmaven-surefire-plugin${maven-surefire-plugin-version}truetruefalselistenerorg.evosuite.runtime.InitializingListener上面的插件主要是用来混合执行手动设计的单元测试用例和使用EvoSuite自动生成单元测试用例上面EvoSuiterequired之后plugin和maven依赖配置好了,就可以使用maven命令自动生成并执行单元测试用例了。mvn-DmemoryInMB=2000-Dcores=2evosuite:generateevosuite:exporttest生成测试用例后,可以手动检查生成的测试用例的正确性。写在***无论是研发还是测试负责集成还是单元测试,选择适合自己项目的mock框架,一方面可以缩短测试代码的编写时间,另一方面可以加快测试代码的执行效率另一方面,同时降低测试成本。代码维护成本。无论是业界通用的mock框架,还是定制化的框架,都可以广泛应用于测试。因为做一个mock框架不是目的,目的是高效设计更多的测试覆盖场景,进一步提升测试效率,保证产品质量,将测试人员从繁重的手工测试中解放出来。当单元测试代码已经准备好后,如何发挥测试代码的作用,如何评估测试代码的效率,如何衡量单元测试的投入产出比等等,将在后续文章中进行解答..【本文来自专栏作者张凯涛微信公众号(凯涛的博客)公众号id:kaitao-1234567】点此查看作者更多好文