2020-05-12Api测试用例参数化
问题: 为什么要做参数化?
1.以注册接口的测试用例为例: 接口参数mobile_phone目前在excel中是写死的,执行脚本测试该接口,下一次如果再重新执行,就要重新改excel中的mobile_phone以及pwd,我们不想每次都手动修改excel,如果能在程序中每次执行脚本都可以自动获取到一个随机的mobile_phone和pwd,这样每次重新执行脚本测试接口,就能保证每个测试用例都能被执行
image.png
-
下图是充值接口,member_id也是写死的,是从数据库中查到这个登陆用户的member_id,如果将member_id进行参数化,直接读进来,这样程序脚本是不是更灵活呢?
image.png
如何进行参数化呢?
1.以注册用例
手机号和密码作为参数化获取,不过这里,我们一般对传入正确手机号,密码进行参数化,错误的手机号,密码不用进行参数化,因为只有正确的数据,下次执行脚本才会有影响(如:手机号被注册过了,就无法注册成功了,那么注册成功的case就不能被执行)
image.png
检验sql 也要替换成参数化
image.png
2.充值接口
image.pngimage.png
如何进行参数化替换?
如何把 {toBeRegisterPassword}替换成具体的值呢?
//获取接口参数
cases.getParams();
//获取到接口参数如下
{"mobile_phone":"${toBeRegisterMobilephone}","pwd":"${toBeRegisterPassword}"}
//问题: 怎么把上面的json 转换下面的json呢???
{"mobile_phone":"13333333333","pwd":"222222"}
解决办法如下:加到常量类,常量类新增如下代码:
public class Constant {
//接口参数字段
public static final String REGISTER_MOBILEPHONE_TEXT="${toBeRegisterMobilephone}";
public static final String REGISTER_PASSWORD_TEXT="${toBeRegisterPassword}";
public static final String LOGIN_MOBILEPHONE_TEXT="${toBeLoginMobilephone}";
public static final String LOGIN_PASSWORD_TEXT="${toBeLoginPassword}";
public static final String MEMEBER_ID="${member_id}";
//接口参数值
public static final String REGISTER_MOBILEPHONE_VALUE="18999999999";
public static final String REGISTER_PASSWORD_VALUE="123456";
public static final String LOGIN_MOBILEPHONE_VALUE="18999999999";
public static final String LOGIN_PASSWORD_VALUE="123456";
}
参数替换字段和参数值如何发生关联呢???
即:REGISTER_MOBILEPHONE_TEXT和REGISTER_MOBILEPHONE_VALUE如何关联起来呢???
解决办法:使用map
(1)AuthenticationUtils.java类中已定义好一个个map
public class AuthenticationUtils {
// 静态变量
public static Map<String, String> env = new HashMap<String, String>();
}
(2)将参数放进map
以登录为例:LoginCase.java类加入以下新增代码
@BeforeSuite
public void init() {
//套件执行初始化:配置参数化变量
AuthenticationUtils.env.put(Constant.REGISTER_MOBILEPHONE_TEXT,Constant.REGISTER_MOBILEPHONE_VALUE);
AuthenticationUtils.env.put(Constant.REGISTER_PASSWORD_TEXT,Constant.REGISTER_PASSWORD_VALUE);
AuthenticationUtils.env.put(Constant.LOGIN_MOBILEPHONE_TEXT,Constant.LOGIN_MOBILEPHONE_VALUE);
AuthenticationUtils.env.put(Constant.LOGIN_PASSWORD_TEXT,Constant.LOGIN_PASSWORD_VALUE);
}
member_id是不能通过初始化加进去,因为member_id是需要通过动态获取的(JSONPath从响应体中获取)
AuthenticatonsUtils.java新增代码
// 2.获取member_id
Object memberId = JSONPath.read(responseBody, "$.data.id");
if (memberId != null) {
env.put("memberId", memberId.toString());
}
public class AuthenticationUtils {
// 静态变量
public static Map<String, String> env = new HashMap<String, String>();
public static void storeToken(String responseBody) {
// 1 取出token---jsonpath提供的语法格式取
// $代表从根元素开始找,一层层找下去
Object token = JSONPath.read(responseBody, "$.data.token_info.token");// 如果找不到返回的是null
// 2 存储token
if (token != null) {
env.put("token", token.toString());// token是Object类型,对象转字符串才可以存进去,因为env Map<String,String> 是String
// 2.获取member_id
Object memberId = JSONPath.read(responseBody, "$.data.id");
if (memberId != null) {
env.put("memberId", memberId.toString());
}
}
}
}
(3)循环遍历key:
map遍历---》这里是把key放到set集合,然后增强for循环进行遍历
一共就有五个key,逐一判断params中是否有这五个key,params中只要有key就替换成value
String params = cases.getParams();// 获取第1个,第2个,第3个......用例接口参数(用例一个个读进来)
Set<String> keySet = AuthenticationUtils.env.keySet();// 获取key集合
//为何要进行遍历:这样做就不用知道params中使用到了哪些参数变量,当读取第一个用例时,通过遍历所有的key,第一个用例params中只要有key就替换成value
for (String key : keySet) {
String keyValue = AuthenticationUtils.env.get(key);// 获取key的值
params.replace(key, keyValue);// params是否包含key,如果有key,就将其替换成keyValue,没有就不替换
// 遍历完所有的key会自动跳出循环
}
上面代码抽取优化到BaseCase.java类中:新增代码如下
/**
* 参数化变量替换
* @param params
* @return
*/
public String parmasReplace(String params) {
Set<String> keySet = AuthenticationUtils.env.keySet();// 获取key集合
// 为何要进行遍历:这样做就不用知道params中使用到了哪些参数变量,通过遍历所有的key(一共就有四个key,逐一判断params中是否有key,params中只要有key就替换成value)
for (String key : keySet) {
String keyValue = AuthenticationUtils.env.get(key);// 获取key的值
params.replace(key, keyValue);// params是否包含key,如果有key,就将其替换成keyValue,没有就不替换
// 遍历完所有的key会自动跳出循环
}
return params;
}
LoginCase.java类进行调用上述方法即可:
image.png
到此代码就写完了,执行LoginCase.java
image.png
从上图可以看到有两个错误,第一:参数没有替换成功, 第二,报了空指针
(1)先看第一个错误,为什么没有被替换成功
图1:可以看到所有的key,按f6执行下一步到图2
image.png
图2:可以看到key对应的value就是图3中的值
image.png
图3:按f6执行到下一步 图4
image.png
图4: 下图key的values是图5
image.png
图5:按f6执行下一步遍历结束后执行 到图6
image.png
图6:下图可以看到,返回值没有被替换
image.png原因定位:params = params.replace(key, keyValue);需用变量接收替换后的值
image.png(2)为什么会报空指针
image.png上图报错,第一行提示报错出在 BaseCase.java:62行,点击此处会自动跳到报错的地方
image.png
分析第62行代码,key,keyValued都有值,不会为空,那么问题就出在传进来参数params可能为空,空对象调方法一般会报空指针,再看报错第二行,可以看到params是哪里传进来的
image.png
上图可以分析得到:传入的sql为空,所以需要优化代码:BaseCase.java类优化参数替换方法parmasReplace()
/**
* 参数化变量替换
*
* @param params
* @return
*/
public String parmasReplace(String params) {
//判断params为空的处理
if (StringUtils.isBlank(params)) {
return "";
}
Set<String> keySet = AuthenticationUtils.env.keySet();// 获取key集合
// 为何要进行遍历:这样做就不用知道params中使用到了哪些参数变量,通过遍历所有的key(一共就有四个key,逐一判断params中是否有key,params中只要有key就替换成value)
for (String key : keySet) {
String keyValue = AuthenticationUtils.env.get(key);// 获取key的值
params = params.replace(key, keyValue);// Primes是否包含key,如果有key,就将其替换成keyValue,没有就不替换
// 遍历完所有的key会自动跳出循环
}
return params;
}
备注:
(1)注册和充值接口参数替换代码自行补充(只需要在各自的类进行调用替换方法即可),即:将下面代码分别copy到RegisterCase.java,RechargeCase.java中即可
//接口参数变量替换
String params = parmasReplace(cases.getParams());
System.out.println("替换后的params:" + params);
//将替换后的params塞到cases对象里
cases.setParams(params);
// sql参数替换
String SQLparams = parmasReplace(cases.getSql());
System.out.println("替换后的SQLparams :" + SQLparams);
//替换的后的sql塞到cases对像里面
cases.setSql(SQLparams);
(2)如果需要单独运行注册,充值接口,则RegisterCase.java,RechargeCase.java各自都要加上下面代码:
@BeforeSuite
public void init() {
// 套件执行初始化:配置参数化变量
AuthenticationUtils.env.put(Constant.REGISTER_MOBILEPHONE_TEXT, Constant.REGISTER_MOBILEPHONE_VALUE);
AuthenticationUtils.env.put(Constant.REGISTER_PASSWORD_TEXT, Constant.REGISTER_PASSWORD_VALUE);
AuthenticationUtils.env.put(Constant.LOGIN_MOBILEPHONE_TEXT, Constant.LOGIN_MOBILEPHONE_VALUE);
AuthenticationUtils.env.put(Constant.LOGIN_PASSWORD_TEXT, Constant.LOGIN_PASSWORD_VALUE);
}
如果通过执行testNG.xml运行,则RegisterCase.java,RechargeCase.java各自就不需要要加上上面的代码(只要有一个类中有上面的代码即可,因为上面代码是在套件执行之前会将变量加到map里面)
疑问:执行充值接口为什么${member_id}没有被替换掉呢?
原因定位:(1)通过打断点,发现map集合key没有MEMBER_ID
这是为什么呢?-》因为:MEMBER_ID是需要通过登陆才能获取
Object memberId = JSONPath.read(responseBody, "$.data.id");
if (memberId != null) {
env.put("memberId", memberId.toString());
}
所以不能单独运行充值接口,需要依赖登录接口,此时运行通过运行testNG.xml,控制台打印结果发现,${member_id}还是没有被替换
通过打断点,发现keySet集合还是没有MEMBER_ID
原因:
public static final String MEMBER_ID="${member_id}";
错误的写法:
Object memberId = JSONPath.read(responseBody, "$.data.id");
if (memberId != null) {
//env.put("memberId", memberId.toString());
// env.put("MEMBER_ID", memberId.toString());
}
正确的写法:
Object memberId = JSONPath.read(responseBody, "$.data.id");
if (memberId != null) {
env.put(Constant.MEMBER_ID, memberId.toString());
}
再次打断点,发现keySet集合有MEMBER_ID(注意:只有member_id不为空才能put进去)
image.png
重新执行testNG.xml。控制台打印结果中$ {member_id}替换成功
待解决问题:为什么下面的写法语法没有报错呢
// env.put("MEMBER_ID", memberId.toString());
java 随机生成手机号码(自己百度)
// 接口参数
public static final String REGISTER_MOBILEPHONE_TEXT = "${toBeRegisterMobilephone}";
public static final String REGISTER_PASSWORD_TEXT = "${toBeRegisterPassword}";
public static final String LOGIN_MOBILEPHONE_TEXT = "${toBeLoginMobilephone}";
public static final String LOGIN_PASSWORD_TEXT = "${toBeLoginPassword}";
public static final String MEMBER_ID = "${member_id}";
// 接口参数值
public static final String REGISTER_MOBILEPHONE_VALUE = getTel();
public static final String REGISTER_PASSWORD_VALUE = "123456789";
public static final String LOGIN_MOBILEPHONE_VALUE = REGISTER_MOBILEPHONE_VALUE;
public static final String LOGIN_PASSWORD_VALUE = "123456789";
public static int getNum(int start, int end) {
return (int) (Math.random() * (end - start + 1) + start);
}
// public static String[] telFirst = "134,135,136,137,138,139,150,151,152,153,154,155,156,157,158,159,130,131,132,133"
// .split(",");
private static String getTel() {
String[] telFirst = "134,135,136,137,138,139,150,151,152,153,154,155,156,157,158,159,130,131,132,133"
.split(",");
// 打印出数组
System.out.println(Arrays.toString(telFirst));
int index = getNum(0, telFirst.length - 1);
String first = telFirst[index];
String second = String.valueOf(getNum(1, 888) + 10000).substring(1);
String third = String.valueOf(getNum(1, 9100) + 10000).substring(1);
return first + second + third;
}
这里有个疑问,为什么定义一个静态全局变量数组【上面被注释掉的代码】,数组是NUll??? 但是放在getTel()方法体方是可以的,这里是为什么呢????
原因如下:
public static final String REGISTER_MOBILEPHONE_VALUE = getTel();这句代码在定义静态数组之前,且这里有调用getTel(),且getTel()方法体有使用到telFirst,而此时程序还没有执行到定义静态数组的语句即还没有被加载进来,所以会报null.
解决办法:把静态数据声明放在最前面,如下图
package com.lemon.constant;
import java.util.Arrays;
public class Constant {
public static String[] telFirst = "134,135,136,137,138,139,150,151,152,153,154,155,156,157,158,159,130,131,132,133"
.split(",");
public static final String EXCEL_PATH = "src/test/resources/case_v6.xlsx";
public static final int ACTUAL_RESPONSE_DATA_CELLNUM = 5;
// 接口参数
public static final String REGISTER_MOBILEPHONE_TEXT = "${toBeRegisterMobilephone}";
public static final String REGISTER_PASSWORD_TEXT = "${toBeRegisterPassword}";
public static final String LOGIN_MOBILEPHONE_TEXT = "${toBeLoginMobilephone}";
public static final String LOGIN_PASSWORD_TEXT = "${toBeLoginPassword}";
public static final String MEMBER_ID = "${member_id}";
// 接口参数值
public static final String REGISTER_PASSWORD_VALUE = "123456789";
public static final String REGISTER_MOBILEPHONE_VALUE = getTel();
public static final String LOGIN_MOBILEPHONE_VALUE = REGISTER_MOBILEPHONE_VALUE;
public static final String LOGIN_PASSWORD_VALUE = "123456789";
public static int getNum(int start, int end) {
return (int) (Math.random() * (end - start + 1) + start);
}
private static String getTel() {
int index = getNum(0, telFirst.length - 1);
String first = telFirst[index];
String second = String.valueOf(getNum(1, 888) + 10000).substring(1);
String third = String.valueOf(getNum(1, 9100) + 10000).substring(1);
return first + second + third;
}
public static void main(String[] args) {
// System.out.println(getTel());
System.out.println(Arrays.toString(telFirst));
}
}