背景
无论是手工测试还是接口测试,我们都需要对测试结果进行校验。面对现在的测试人员越来越高的要求,我们不能再只是简单的去校验那些code、response_status字段,而是应该深入到对reponse每一个字段、数据库、缓存等业务底层信息的校验。因此,原先的校验方法已经不太适用。
原先的校验方法
以testng为例,原先一般接口测试代码的书写逻辑如下
public class CheckAccountEnableWithdrawTest extends TestBase {
@Resource
IMerchantWalletManageFacade merchantWalletManageFacade;
@Test(dataProvider = "testData")
public void testCheckAccountEnableWithdraw(String entityId, String withdrawAmt, AccountType accountType, boolean success){
boolean result = merchantWalletManageFacade.checkAccountEnableWithdraw(entityId,withdrawAmt,accountType); // 调用接口方法,相当于调用request方法,得到返回结果
Assert.assertEquals(result,success); // 校验response值和期望的success值是否相同
}
@DataProvider
public Object[][] testData(){
return new Object[][] {
{"99932014","1",AccountType.GUARANTEE,true}, // 是否可以提取担保账户,可以提取,期望返回true
{"99932014","1",AccountType.PENDING_SETTLEMENT,false}, // 是否可以提取待结算账户,待结算账户不能直接体现,期望返回false
};
}
}
因为校验逻辑比较简单,只校验一个字段,直接这么写也没有什么问题。但是如果我要加入新的校验逻辑呢?
@Test(dataProvider = "testData")
public void testCheckAccountEnableWithdraw(String entityId, String withdrawAmt, AccountType accountType, boolean success){
boolean result = merchantWalletManageFacade.checkAccountEnableWithdraw(entityId,withdrawAmt,accountType);
Assert.assertEquals(result,success);
if(result && success) {
// 校验数据库和缓存
}
else {
// 校验报错信息
}
}
上面的写法是一个可以参考的方法,但是这是我们设计测试用例的初衷吗?一条具体测试用例的设计,必然是单一路径的,不应该再加入任何的if else之类的逻辑,这违反了用例的单一职责原则。那么该如何设计我们的测试用例呢?
我们期望的是传递一个校验器给我们的测试用例,校验器里定义了我们的校验方法。怎么书写我们的校验器呢?我们需要传给测试用例一个“函数”,这个函数里定义了校验方法,这将用到jdk1.8里的函数式接口。
函数式接口
函数式接口是jdk1.8的新特性,结合stream等新功能,是java的一个里程碑式的新功能。所有的函数式接口,必然被FunctionalInterface注解所修饰。如Function接口所示。Function接口就是定义了一个“给定一个参数并且有返回值”的函数
@FunctionalInterface
public interface Function<T, R> {
/**
* 用给定的参数调用这个函数
*
* @param t 函数的参数
* @return 函数的返回值
*/
R apply(T t);
}
再看个BiFunction的接口,定义一个“传递两个参数并返回值”的函数
@FunctionalInterface
public interface BiFunction<T, U, R> {
/**
* Applies this function to the given arguments.
*
* @param t 第一个参数
* @param u 第二个参数
* @return 返回值
*/
R apply(T t, U u);
}
下面是一个BiFunction函数式接口的例子。具体逻辑看注释。
public class MyTest2 {
public static void main(String[] args) {
// 定义加法, jdk1.7写法。实现apply方法,integer1和integer2为该方法传递的参数,return值为方法的返回值
BiFunction<Integer,Integer,Integer> addFunction = new BiFunction<Integer, Integer, Integer>() {
@Override
public Integer apply(Integer integer1, Integer integer2) {
return integer1 + integer2;
}
};
// 定义减法,BinaryOperator是BiFunction的子类,如果传参和返回值类型相同,可以用BinaryOperator代替BiFunction
BinaryOperator<Integer> subFunction = new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer integer, Integer integer2) {
return integer - integer2;
}
};
// 定义乘法,如果一个接口被@FunctionalInterface注解修饰,那么它可以被简化为lambda表达式。
// 表达式左面是传递的参数,右边是被{}包围的函数体。如果函数体是单语句,那么单语句的执行结果就是返回值
BinaryOperator<Integer> mulFunction = (integer, integer2) -> integer * integer2;
BinaryOperator<Integer> divFunction = (integer, integer2) -> integer / integer2;
Integer i1 = 4;
Integer i2 = 2;
System.out.println(run(i1,i2,addFunction)); // 加法,输出6
System.out.println(run(i1,i2,subFunction)); // 减法,输出2
System.out.println(run(i1,i2,mulFunction)); // 乘法,输出8
System.out.println(run(i1,i2,divFunction)); // 除法,输出2
}
/**
* 给定两个参数,传递一个函数,分别实现对这两个参数的加减乘除操作
* @param int1 参数1
* @param int2 参数2
* @param opmethod BiFunction 定义一个(传递两个参数并且返回一个值)的函数
* @return
*/
public static Integer run(Integer int1, Integer int2, BiFunction<Integer,Integer,Integer> opmethod) {
return opmethod.apply(int1,int2);
}
}
怎么写测试用例
把校验器方法的具体实现写在@DataProvider中,在@Test方法中调用校验器方法,因为校验器只需传递一个Result值(接口的response)而不需要返回参数,可采用Consumer接口来实现,Consumer接口只需要实现一个void accept(T t)方法即可。这个方法即我们的测试用例校验方法
public class GetMyCommissionTest extends TestBase {
@Resource
ICommissionAccountFacade commissionAccountFacade;
@Resource
Config config;
@Resource
DruidDataSource accountDataSource;
@Resource
DruidDataSource marketAgentDataSource;
@DataProvider // 具体的用例设计
public Object[][] testData() {
return new Object[][]{
{config.getCustomerRegisterId(), new Consumer<Result<CommissionVO>>() {
@Override
public void accept(Result<CommissionVO> result) {
System.out.println(JSON.toJSONString(result));
Assert.assertTrue(result.isSuccess());
Assert.assertNotNull(result.getModel().getBalanceCommission());
Assert.assertNotNull(result.getModel().getPendingCommission());
DBHelper.setdruidDataSource(accountDataSource);
// 分库分表,要多次查询, 先查询到账号名称
List<DataMap> accountList = DBHelper.executeQuery(String.format("select * from inst_account_binding where brh_id='%s'",huaishiConfig.getCustomerRegisterId()));
Assert.assertEquals(accountList.size(),1);
Long accountNo = accountList.get(0).getLongValue("account_no");
// 再查询账户,查个人冻结账户和个人结算账户的余额,balanceCommission为这两者之和
List<DataMap> accountBaseInfoList = DBHelper.executeQuery(
String.format("select avail_bln from account_base_info where account_no=%s and account_type in ('010', '011') ", accountNo));
Assert.assertEquals(result.getModel().getBalanceCommission(),accountBaseInfoList.stream().map(dataMap -> dataMap.getLongValue("avail_bln")).reduce(0L,Long::sum));
// 查询待结算账户,从marketagentsoa里查询分销的待结算金额
DBHelper.setdruidDataSource(marketAgentDataSource);
// 先查询distributorId
List<DataMap> dataMaps = DBHelper.executeQuery("select id from d_distributor where user_id='"+ huaishiConfig.getCustomerRegisterId() +"' and is_valid=1");
Assert.assertEquals(dataMaps.size(),1);
String distributorId = dataMaps.get(0).getStringValue("id");
List<DataMap> pendingList = DBHelper.executeQuery("select * from d_account where distributor_id = " + distributorId);
Assert.assertEquals(pendingList.size(), 1);
Assert.assertEquals(result.getModel().getPendingCommission().longValue(),pendingList.get(0).getLongValue("unsettled_balance"));
}
}}, // 存在的账户,从account库获取已入账和待入账金额,从marketagent库获取分销待结算金额
{"not exist account", new Consumer<Result<CommissionVO>>() {
@Override
public void accept(Result<CommissionVO> commissionVOResult) {
Assert.assertTrue(commissionVOResult.isSuccess());
Assert.assertEquals(commissionVOResult.getModel().getPendingCommission().longValue(),0L);
Assert.assertEquals(commissionVOResult.getModel().getBalanceCommission().longValue(),0L);
}
}}, // 不存在的账户,入账金额和待结算金额都为0
};
}
@Test(dataProvider = "testData")
public void testGetMyCommission(String customerId, Consumer<Result<CommissionVO>> validator) {
Result<CommissionVO> result = commissionAccountFacade.getMyCommission(customerId);
validator.accept(result); // 只需调用校验器方法
}
}