Mockito+JMockit+TestNG单元测试实践总结

发表于:2018-3-05 11:34

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:Zeng_小洲    来源:简书

  单元测试实践背景
  · 测试环境定位bug时,需要测试同学协助手动发起相关业务URL请求,开发进行远程调试
  问题:
  1、远程调试影响测试环境数据正常获取,影响测试同学测试进度
  2、远程调试代码有时并非最新代码,与本地不一致增加调试难度,往往需要发最新的包再调试
  3、controller层请求参数依赖特定客户端版本发起,其他版本回归验证,增加模拟操作成本
  · 依赖第三方系统,第三方系统请求不稳定或希望第三方接口返回特定数据
  为什么需要单测
  编写单元测试代码并不是一件容易的事情,那为什么还需要去话费时间和精力来编写单元测试呢?
  减少Bug:如今的项目大多都是多人分模块协同开发,当各个模块集成时再去发现问题,定位和沟通成本是非常高的,通过单元测试来保证各个模块的正确性,可以尽早的发现问题,而不时等到集成时再发现问题。
  放心重构:如今持续型的项目越来越多,代码不断的在变化和重构,通过单元测试,开发可以放心的修改重构代码,减少改代码时心理负担,提高重构的成功率。
  改进设计:越是良好设计的代码,一般越容易编写单元测试,多个小的方法的单测一般比大方法(成百上千行代码)的单测代码要简单、要稳定,一个依赖接口的类一般比依赖具体实现的类容易测试,所以在编写单测的过程中,如果发现单测代码非常难写,一般表明被测试的代码包含了太多的依赖或职责,需要反思代码的合理性,进而推进代码设计的优化,形成正向循环。
  个人感受,将controller层请求参数抽取管理后,debug不依赖客户端与测试环境,能够迅速在本地执行定位问题;同时,单元测试提供测试数据准备与模拟特定测试数据返回,对业务测试起辅助作用。
  单元测试需要理解的几个概念
  被测系统:SUT(System Under Test)
  被测系统(System under test,SUT)表示正在被测试的系统,目的是测试系统能否正确操作。这一词语常用于软件测试中。软件系统测试的一个特例是对应用软件的测试,称为被测应用程序(application under test,AUT)。
  SUT也表明软件已经到了成熟期,因为系统测试在测试周期中是集成测试的后一阶段。
  测试替身:Test Double
  在单元测试时,使用Test Double减少对被测对象的依赖,使得测试更加单一。同时,让测试案例执行的时间更短,运行更加稳定,同时能对SUT内部的输入输出进行验证,让测试更加彻底深入。但是,Test Double也不是万能的,Test Double不能被过度使用,因为实际交付的产品是使用实际对象的,过度使用Test Double会让测试变得越来越脱离实际。
  要理解测试替身,需要了解一下Dummy Objects、Test Stub、Test Spy、Fake Object 这几个概念,下面我们对这些概念分别进行说明。
  Dummy Objects
  Dummy Objects泛指在测试中必须传入的对象,而传入的这些对象实际上并不会产生任何作用,仅仅是为了能够调用被测对象而必须传入的一个东西。
  Test Stub
  测试桩是用来接受SUT内部的间接输入(indirect inputs),并返回特定的值给SUT。可以理解Test Stub是在SUT内部打的一个桩,可以按照我们的要求返回特定的内容给SUT,Test Stub的交互完全在SUT内部,因此,它不会返回内容给测试案例,也不会对SUT内部的输入进行验证。
  Test Spy
  Test Spy像一个间谍,安插在了SUT内部,专门负责将SUT内部的间接输出(indirect outputs)传到外部。它的特点是将内部的间接输出返回给测试案例,由测试案例进行验证,Test Spy只负责获取内部情报,并把情报发出去,不负责验证情报的正确性。
  Mock Object
  Mock Object和Test Spy有类似的地方,它也是安插在SUT内部,获取到SUT内部的间接输出(indirect outputs),不同的是,Mock Object还负责对情报(intelligence)进行验证,总部(外部的测试案例)信任Mock Object的验证结果。
  Fake Object
  经常,我们会把Fake Object和Test Stub搞混,因为它们都和外部没有交互,对内部的输入输出也不进行验证。不同的是,Fake Object并不关注SUT内部的间接输入(indirect inputs)或间接输出(indirect outputs),它仅仅是用来替代一个实际的对象,并且拥有几乎和实际对象一样的功能,保证SUT能够正常工作。实际对象过分依赖外部环境,Fake Object可以减少这样的依赖。
  看完Test Double这几个概念后,是不是一头雾水?以下通俗解释,Dummy Objects就不做解释了。
  Test Stub
  系统测试需要某一指定数据返回时,开发将获取数据逻辑代码替换成指定数据,发包测试完再替换回原来逻辑。替换代码返回指定数据,这就是测试桩。
  Test Spy
  Test Stub只返回指定内容给SUT,并没有指定返回测试案例,所以我们引入单元测试,在单元测试用例调用引用该插桩的方法。
  这时我们能获测试桩间接输出内容,甚至是报错信息,再也不用到服务器查找错误日志了,这就是Test Spy。
  Mock Object
  Mock Object就是在Test Spy的基础上,加入验证机制。调用引用该插桩的方法,我们要确保这个插桩正常被执行或指定执行n次,得到的结果是不是我们期望的结果,mock就以此为生。
  Fake Object
  Fake Object相对Test Stub,是一个面向对象概念。我们只希望替换掉一个实际被引用对象里面的一个方法返回值,被替换某个方法返回值的对象就叫Fake Oject,它与实际对象一样的功能。Mock Object也囊括Fake Object概念,可以看出Test Stub < Fake Object < Mock Object。
  Mock框架模型
  测试验证过程,我们不可能每次都修改代码stub一个方法,发包验证完后再改回,发布外网回归验证阶段这种操作根本不被允许。Mock框架应运而生,我们在单元测试用例stub一个方法后,将之注入被测系统SUT,这个注入只会在test spy阶段产生影响。
  市面上很多mock框架,Jmockit、Mockito、PowerMock、EasyMock等,大体遵循record-replay-verify模型设计,有些地方称之为expect-run-verify模式(期望--运行--验证),有些地方称之(AAA阶段)Arrange 、Act、Assert,大体一个意思。很明显,Mock框架的应用过程,我们先需要指定stub,然后运行被测方法,然后在验证stub的正确性,这个过程就称之为mock。
  单元测试框架选择
  Testng
  TestNG与Junit很相似, 但testng更加灵活,以下为两者对比。
  [图片上传失败...(image-93566-1513052813178)]
  参考 JUnit 4 Vs TestNG比较
  · Testng支持分组测试
  · Testng参数化测试支持复杂类型参数,而junit只支持基本类型
  · Testng提供XML灵活配置测试运行套件
  · Testng支持依赖测试
  · Testng支持并发测试,上面文章未讲到的,补充下。如@Test(threadPoolSize=3,invocationCount=6,timeout=500),而Junit的话可以引入JunitPref框架。
  Jmockit
  Jmockit是一个功能很强大的框架,可以mock静态方法、final类、抽象类、接口、构造函数等,几乎无所不能,但编程语言不够简洁。
  Jmockit的介绍和使用
  这里需要补充的点:
  · 注解@Tested,标识的被测对象实例, @Injectable的实例会自动注入到@Tested中,有时候在事件过程中实在无法注入,可以借助spring的反射工具ReflectionTestUtils进行注入。
  · Expectations:期望,指定的方法必须被调用,且方法默认次数为1。如果指定打桩的方法在test用例不被调用,或者调用次数超过1,则会报错,建议使用NonStrictExpectations配合Verifications使用。
  · Expectations(T)/NonStrictExpectations(T),Expectations(.class){}这种方式只会模拟区域中包含的方法,这个类的其它方法将按照正常的业务逻辑运行,T就变成了一个Fake Object。
  · MockUp(T)中,未mock的函数不受影响,T也是一个Fake Object。通常rpc接口(接口无具体实现方法)、构造函数通过MockUp进行局部方法mock。
  以下主要演示一个rpc接口的mock。
public class ColumnArticlesControllerTest2 extends BaseContorllerMockTest {
private MockMvc mockMvc;
@Autowired
private ConfigService configService;
@Autowired
private ICpDataKievHandler cpDataKievHandler;
@Autowired
private IndexArticlesDaoCacheImpl indexArticlesDao;
@Autowired
private ColumnArticlesController columnArticlesController;
@BeforeMethod()
public void setUp() throws Exception {
mockMvc = MockMvcBuilders.standaloneSetup(columnArticlesController).build();
}
// CSV最好使用gbk格式,目前不支持默认路径,CSV文件位于到dataprovider目录下
@Test(description = "测试list.do接口", dataProvider = "genData", dataProviderClass = CommonDataProvider.class)
@Csv("/dataprovider/ColumnArticlesControllerTest/testGetColumnArticleList.csv")
public void testGetColumnArticleList(String cpChannelId, long columnId, String ucParam, Integer v, String flymeuid,
String nt, String vn, String deviceinfo, String deviceType, String os, Integer supportSDK, Integer cpType)
throws Exception {
String imei = deviceinfo.substring(deviceinfo.indexOf("imei="), deviceinfo.indexOf("&"));
ArticleView params = new ArticleView();
params.setCpChannelId(cpChannelId);
params.setColumnId(columnId);
params.setUcparam(ucParam);
params.setClientReqId(System.currentTimeMillis() + imei);
CommonParams commonParams = new CommonParams();
commonParams.setV(v);
commonParams.setFlymeuid(flymeuid);
commonParams.setNt(nt);
commonParams.setVn(vn);
commonParams.setDeviceinfo(DeviceUtil.deviceToEncrypt(deviceinfo));
commonParams.setDeviceType(deviceType);
commonParams.setOs(os);
System.out.println(configService.getConfigValue(ConfigKeyEnum.UC_VIDEO_PER));
// jmock静态方法mock掉ip,防止http请求获取Ip报错
new NonStrictExpectations(WebUtils.class, configService) {
{
WebUtils.getClientIp();
result = "172.17.132.66";
}
{
// 后台控制百分比,返回0则过滤掉类型为27的视频,返回100则放开下发该视频“XXX键盘”
configService.getConfigValue(ConfigKeyEnum.UC_VIDEO_PER);
result = "100";
}
};
final ICpDataKievHandler cpDataKievHandler2 = cpDataKievHandler;
try {
String video27Articles = FileUtils
.getFileText(FileUtils.getCurrentProjectPath() + "/src/test/resources/afdata/video27Articles.json");
final CpDataResult value = JSON.parseObject(video27Articles, CpDataResult.class);
cpDataKievHandler = new MockUp<ICpDataKievHandler>() {
@mockit.Mock
CpDataResult getUCArticleList(String imei, long channelId, String method, String recoid, long ftime,
String cityCode, String cityName, int pageSize) {
return value;
}
}.getMockInstance();
ReflectionTestUtils.setField(indexArticlesDao, "cpDataKievHandler", cpDataKievHandler);
System.out.println(JSON
.toJSON(columnArticlesController.getColumnArticleList(params, supportSDK, cpType, commonParams)));
} finally {
//mock完还原接口方法取值,避免影响其他用例
ReflectionTestUtils.setField(indexArticlesDao, "cpDataKievHandler", cpDataKievHandler2);
}
}
上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理。
21/212>
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号