引言
执行效率
目前使用的测试框架是基于spring,被测接口是dubbo的服务。dubbo的架构如
服务使用方的初始化需要经历以下这几个步骤:监听注册中心连接服务提供端创建消费端服务代理本地调试用例时,发现速度非常慢,运行一个用例需要30s,而实际执行用例逻辑的时间大概在1s左右,主要时耗在服务消费者的初始化阶段。测试工程中,各服务的test类继承了同一个基类,基类里面做了各服务的初始化的步骤。在对接的服务数目较少时,需要初始化的对象较少,对用例运行的影响并不大,但随着业务的增多,服务数目也增多,导致跑A服务接口的用例时把大量未用到的B服务、C服务也一起初始化了,导致整体时耗大大增加。解决办法:在运行用例时只初始化需要的服务使用方,减少不必要的初始化开销。
用例编写和维护
一个用例示例
用例编写的基本步骤为:
step1:准备数据构造新建会员卡和更新会员卡的对象step2:执行创建会员卡step3:执行更新会员卡step4:检查更新结果step5:清理创建的会员卡
转换成代码为:
@Test
public void testUpdate() {
try {
/*
* 创建新建和更新的卡对象
*/
CardCreateDescriptionDTO descCreate = new CardCreateDescriptionDTO();
descCreate.setName(xxxx);
//此处省略若干参数设置过程....
CardUpdateDescriptionDTO descUpdate = new CardUpdateDescriptionDTO();
descUpdate.setName(xxxxx);
//此处省略若干参数设置过程....
/*
* 新建会员卡
*/
cardAlias = cardService.create((int) kdtId, descCreate,operator).getCardAlias();
/*
* 更新会员卡
*/
cardService.update(kdtId, cardAlias, descUpdate, operator);
/*
* 校验编辑是否生效
*/
CardDTO cardDTO = cardService.getByCardAlias(cardAlias);
Assert.assertEquals(cardDTO.getName(), xxxx, '会员卡更新失败');
//此处省略若干参数校验过程....
} catch (Exception e) {
Assert.assertNull(e);
} finally {
try {
if(cardAlias!=null) {
cardService.deleteByCardAlias((int) kdtId, cardAlias, operator);
}
} catch (Exception e) {
Assert.assertNull(e, e.getMessage());
}
}
}
123456789101112131415161718192021222324252627282930313233343536373839
按照预期的步骤去写这个case,可以满足要求,但是如果需要扩展一下,编写诸如:更新某种类型的会员卡、只更新会员卡的有效期这样用例的时候,就会觉得按这个模式写case实在太长太啰嗦了,痛点在以下几个地方:
数据准备比较麻烦,需要逐一设值数据检查部分逐字段检查,心好累每个创建相关的用例都需要清理资源,每次都需要做一次,太重复了
用例本身关注的是更新这个操作,却花了太多时间和精力在其他地方,很多是重复劳动。代码编写里有一个重要原则,DRY,即所有重复的地方都可以考虑抽象提炼出来。
三段式用例
可以将大部分用例的执行过程简化为三个部分:数据准备执行操作结果检查用简单的三个部分来完成上述用例的改写:数据准备:
@DataProvider(name='dataTestUpdate')
public Object[][] dataTestUpdate() {
return new Object[][]{
{cardFactory.genRuleNoCreate(...),cardFactory.genRuleNoUpdate(...)},
{cardFactory.genRuleCreate(...),cardFactory.genRuleUpdate(...)},
{cardFactory.genPayCreate(...),cardFactory.genPayUpdate(...)}
};
}
123456789
执行操作+结果检查
Test(dataProvider = 'dataTestUpdate')
public void testUpdate(CardCreateDescriptionDTO desc,CardUpdateDescriptionDTO updateDesc){
try {
/*
* 执行操作:创建+更新
*/
//创建会员卡
CardDTO cardBaseDTO = createCard(kdtId,desc,operatorDTO);
cardAlias=cardBaseDTO.getCardAlias();
recycleCardAlias.add(cardAlias); //将卡的标识放入垃圾桶后续进行回收
CardDTO ori = getCard(kdtId,cardAlias);
//更新会员卡
updateCard(kdtId,cardAlias,updateDesc,operatorDTO);
CardDTO updated = getCard(kdtId,cardAlias);
/*
* 结果检查
*/
checkUpdateCardResult(ori,updated,updateDesc,kdtId);
} catch (Exception e) {
Assert.assertNull(e);
}
12345678910111213141516171819202122
其中可行的优化点将在下面娓娓道来。
测试数据的优化
在这个用例中,数据准备的部分使用了dataProvider来复用执行过程,这样不同参数但同一过程的数据可以放在一个case里进行执行和维护。数据生成使用了工厂方法CardFactory,好处是简化了参数,避免了大量set操作;另一方面,根据实际的业务场景,可以考虑提供多个粒度的构造方法,比如以下两个构造方法需要提供的参数差别很大:
第一个主要用在验证创建接口的场景,检查各个传入的参数是否生效。publicCardCreateDescriptionDTOgenRuleCreate(BooleanisPost,Integerdiscount,Longrate,LongpointsDef,StringcouponIds,Longnum,Longgrowth,LongtermToCardId,Longamount,Longpoints,Longtrad{12第二个用在如删除的场景,所以只需要一个创建好的会员卡对象,并不是很关注创建的内容是什么。publicCardCreateDescriptionDTOgenRuleSimpleCreate(Stringnam{1在上面的优化过的用例中,能够执行更新操作的前置条件是需要有一个已经创建的会员卡,在实际用例编写的时候通过直接创建一个会员卡,然后执行更新完成后再回收删除这张会员卡来满足这个条件。另一种提供满足操作所需前置数据的方式是预置数据。以下情况可以考虑预置数据的方式:提高用例稳定性,解依赖,加快执行速度需要对特定的类型、状态的对象进行查询创建或者构造比较麻烦典型的场景:比如编写查询的用例时预先创建满足条件的对象供查询用例使用。谈到预置数据,不得不谈的一个问题是数据管理。在编写用例的时候,'我们往往需要一个__的资源',框框里面的即是对数据的描述和要求,比如我需要一个全新的账号,一个支付过的订单号,一张免费的会员卡,来完成我们的用例。所以需要对数据进行标记而不是简单硬编码的方式在用例中使用。如:通过特定名字的变量名和数据进行关联。/**只做查询卡,不做领卡删卡*/publicLongqueryCardUid=DataMocker.MOCK_YZUIget;/**用户卡类操作,领卡删卡*/publicLongtakeCardUid=DataMocker.MOCK_YZUIget;/**退款用*/publicLongrefundCardUid=DataMocker.MOCK_YZUIget;123456对数据进行标记后,会发现有一部分数据是用来验证写操作,有一部分数据是查询使用。如果数据又要被写操作的case使用,又要被读操作的case使用,那么写操作的问题和异常就会影响读操作case的执行结果。在代码工程中,可以进行约定,将读写用到的资源进行分离来降低数据的耦合:查询case用的账号不做更改对象的操作查询case用的对象不做修改、删除的操作验证增、删、改行为的资源使用特定账号,且资源最后做回收删除处理(因为资源总数有限)
用例执行完成后需要清理资源。这里的清理资源采用的是一个全局的list的方式保存需要清理的资源信息,在用例执行过程中往里增加数据:,然后用对应的方法取其中的数据进行删除,类似垃圾桶。与原有执行完就执行清理动作相比,使用垃圾桶更加灵活,可以选择控制下清理频率。比如每次在AfterMethod或AfterClass中去清理。
//统一回收
@AfterMethod
public void tearDownMethod() {
for(int i =0;i对方法的适度封装
在实际编写用例的时候,有两个地方可以考虑进行方法封装,从来简化调用,方便维护:
文章为作者独立观点,不代表股票交易接口观点