TestNG搭建接口自动化(二)
上节内容传送门 https://blog.csdn.net/weixin_44025411/article/details/122780210
上节主要讲了如何进行收集测试后的结果信息
TestCase基类的设计思路
- 我们在测试的过程中,“造数据”是不可避免的。
比如我们需要测试一个动态的点赞功能,那我们就需要造出来多种类型的“动态”,图文、视频,文字长短、视频长短各种各样的。发动态的接口,我们又是必须要测试的,因此,如果可以在写这个接口的时候,顺带把“造动态”的功能涵盖进去,就完美了。
- 我们需要多维度的数据。
比如,针对某一个服务的接口通过率;总api覆盖率;某一个测开人员的自动化用例数量;甚至是断言与用例比等等;
- 多套测试环境,如何一键切换环境
这个解决思路有多种,可以写进代码里,写好切换函数,一键切换;也可以配置到Jenkins参数中,进行动态配置;此处我采用的方法是使用Apollo配置中心进行统一管理。也可以借助配置中心进行工具类自动装配。
一、TestCase第一层基类 —— 模板设计&工具类实例的自动装配与销毁
话不多说,上代码
package com.base.test.tng.framework;
import cn.hutool.core.util.StrUtil;
import com.base.test.tng.framework.entity.ApiGan;
import com.base.test.tng.framework.entity.BulletMes;
import com.base.test.tng.framework.entity.ResultMes;
import com.base.test.tng.tools.OmnipotentTool;
import org.testng.SkipException;
import org.testng.annotations.Test;
/**
* @Description: 设计case的模板流程
* @Author: Joe Throne
* @CreatedTime: 2022-02-05 16:56
* 模板设计模式,测试一个case的过程中 必要的对象,创建和销毁
*/
public abstract class BaseQTNGCase {
protected ApiGan apiGan = null;
protected ResultMes resultMes;
protected OmnipotentTool omnipotentTool;
public final ResultMes fire(BulletMes bulletMes){
onStart();
beforeRun(apiGan,bulletMes,omnipotentTool);
resultMes = howToShot(apiGan,bulletMes,omnipotentTool);
afterRun(apiGan,bulletMes,omnipotentTool,resultMes);
onFinish();
return resultMes;
}
/**
* 1、将工具类进行实例化
* 2、将必要的参数进行初始化,如:url、服务信息等填写
*/
protected void onStart(){
apiGan = initGan();//先初始化枪杆
if(apiGan == null || StrUtil.isEmpty(apiGan.getHost())){
//如果apiGan没有初始化,或者主机地址为空字符串,抛出跳过异常
throw new SkipException("apiGan没有初始化");
}
omnipotentTool = new OmnipotentTool(apiGan);//将枪杆中信息给到万能工具类
}
protected void beforeRun(ApiGan apiGan,BulletMes bulletMes,OmnipotentTool omnipotentTool){}
protected abstract ResultMes howToShot(ApiGan apiGan,BulletMes bulletMes,OmnipotentTool omnipotentTool);
/**
* 必须要让最终case进行继承并实现
* @return
*/
protected abstract ApiGan initGan();
protected void afterRun(ApiGan apiGan, BulletMes bulletMes, OmnipotentTool omnipotentTool,ResultMes resultMes){}
private void onFinish() {
apiGan = null;
omnipotentTool.destory();//摧毁万能工具类中的所有对象
omnipotentTool = null;
}
}
核心方法是以下几个:
onStart();
beforeRun(apiGan,bulletMes,omnipotentTool);
howToShot(apiGan,bulletMes,omnipotentTool);
afterRun(apiGan,bulletMes,omnipotentTool,resultMes);
onFinish();
- onStart():将Gan方法回调的信息进行全局初始化,并使用apiGan作为参数给到万能工具类OmnipotentTool,万能工具类内部再次进行传递,根据服务信息从Apollo上获取对应的信息,实例化OmnipotentTool内部的mq/redis/mysql/http/es等工具类
- beforeRun():在case执行前的一些筹备工作,如,调用其他依赖的接口
- howToShot():形参中都是已经实例化的对象,可以直接拿来使用,具体的case执行方案,利用本函数内的形参信息中进行获取。抽象方法需要子类进行实现
- afterRun():执行完case后的操作,如:将数据库等信息还原
- onFinish():将当前的万能工具类销毁
二、TestCase第二层基类 —— 断言的简单封装
这个很简单,没什么可说的。
就是在case执行时,如果调用封装出来的断言,就会维护一个计数器,将断言计数+1
package com.base.test.tng.framework;
import org.testng.Assert;
/**
* @Description: 这一层主要做自定义断言,方便为断言数量进行计数
* @Author: Joe Throne
* @CreatedTime: 2022-02-05 22:05
*/
public abstract class BaseQTNGAssertCase extends BaseQTNGCase{
protected int methodAssertNum;
protected void AssertQTNGEquals(Object object,Object object2){
methodAssertNum ++;
Assert.assertEquals(object,object2);
}
protected void AssertQTNGEquals(String s1,String s2){
methodAssertNum ++;
Assert.assertEquals(s1,s2);
}
protected void AssertQTNGEquals(int code1,int code2){
methodAssertNum ++;
Assert.assertEquals(code1,code2);
}
protected void AssertQTNGNotEquals(int code1,int code2){
methodAssertNum ++;
Assert.assertNotEquals(code1,code2);
}
protected void AssertQTNGNotEquals(String s1,String s2){
methodAssertNum ++;
Assert.assertNotEquals(s1,s2);
}
}
三、TestCase第三层基类 —— Case执行后的信息收集
package com.base.test.tng.framework;
import com.base.test.tng.report.Constant;
import com.base.test.tng.report.entity.ResultMethodDetailEntity;
import org.testng.Assert;
import org.testng.ITestResult;
import org.testng.SkipException;
import org.testng.annotations.*;
import java.util.ArrayList;
import java.util.List;
/**
* @Description: 直接被case继承的基类
* @Author: Joe Throne
* @CreatedTime: 2021-12-19 1:54
* 1、自定义断言,便于统计当前case中的断言数量,便于对比断言和case比例。
* 2、对一个class执行的时间进行计时,针对时间较长的进行拦截并跳过
*/
public abstract class BaseTestCase extends BaseQTNGAssertCase{
@BeforeMethod
public void qngBeforeMethod(){
//将断言计数器进行初始化
methodAssertNum = 0;
}
/**
* 1、如果当前Case超时,就直接判定为执行失败。修改Mes为失败
* 2、收集当前Case的apiGan信息,用于后期的统计
* 3、收集当前Case运行过后的methodAssertNum信息,用于后期的统计
*/
@AfterMethod(alwaysRun = true)
public void qngAfterMethod(ITestResult testResult){
testResult.setAttribute(Constant.CASE_GAN,apiGan); //将api枪杆进行收集
if(testResult.getStatus() == ITestResult.SUCCESS){
//case执行成功了,还需要判断它的执行时间是否达标
boolean isOverTime = testResult.getEndMillis() - testResult.getStartMillis() > Constant.CASE_MAX_TIME;
if(isOverTime){
//如果当前Case超时
testResult.setStatus(ITestResult.SKIP);
testResult.setThrowable(new SkipException("Case超出自定义预期时间"));
}
}
testResult.setAttribute(Constant.METHOD_ASSERT_NUM,methodAssertNum);
}
}
四、TestCase编写Demo
package com.base.test.tng.testcase.server1;
import com.base.test.tng.framework.BaseTestCase;
import com.base.test.tng.framework.QTNGTestAnn;
import com.base.test.tng.framework.entity.ApiGan;
import com.base.test.tng.framework.entity.BulletMes;
import com.base.test.tng.framework.entity.ResultMes;
import com.base.test.tng.tools.OmnipotentTool;
import org.testng.ITestResult;
import org.testng.SkipException;
import org.testng.annotations.*;
import java.util.HashMap;
import java.util.Map;
/**
* @Description:
* @Author: Joe Throne
* @CreatedTime: 2021-12-19 1:49
* 为了使case日后有更大的拓展性,要求:
* 1、howToShot中完全使用形参自动注入的内容进行
* 2、测试类中,需要构建出一个,子弹消息,调用fire
* 3、通过fire获得的返回值中进行断言
* 4、一个类中允许有多个case,但是不建议(只是不建议而已)有多个api使用
*/
public class TestCase1 extends BaseTestCase{
//case中不需要全局变量
@Override
protected ResultMes howToShot(ApiGan apiGan, BulletMes bulletMes, OmnipotentTool omnipotentTool) {
//此处是具体编写测试逻辑的地方
//使用ApiGan中的host和url等信息,再加上万能工具类omnipotentTool,进行http或rpc或mq或db操作,完成测试链路
//在获得请求返回结果后,封装为ResultMes对象进行返回。
//编写时需要注意,如果有一些sql等,需要使用形参进行拼接
return null;
}
@Override
protected ApiGan initGan() {
//此处填写服务和api相关信息
//此处写的信息将会直接装填到howToShot的形参中
return new ApiGan("server1","http://localhost:8090","/user/login");
}
@Test
public void testLogin(){
BulletMes bulletMes = new BulletMes(); //初始化子弹消息
bulletMes.setUsername("joethrone"); //如果需要登录信息,交给HTTP请求的工具类进行完成
bulletMes.setUsername("qwerqwer");
ResultMes resultMes = fire(bulletMes);
AssertQTNGEquals(200,200); //使用父类的断言,便于最终进行断言数量收集
AssertQTNGEquals(200,201); //遇到一个失败的断言,后续的就不会再执行了,断言数量的统计也就到此为止了
}
}
预留拓展点:
当接口自动化引入web依赖,做web服务之后,每一个TestCase,只需要外部传入一个BulletMes,就可以变成一个“造数场景”。