介绍一下整体我是如何做这个框架怎么怎么用的,TestNG单元测试框架,用来执行case,用例我是在Excel里面进行管理的通过poi进行读取,用ExtentTestReport来生成测试报告。
扩展:用例管理也可以用Mysql等数据库进行管理,通过Mybatis框架进行操作。
下面就看一下我只如何读取用例,执行用例,获取结果,生成测试报告吧
1.首先通过封装的Excel工具类读取case
/**
* @return java.lang.Object[][]
* @Author ChengZhiHao
* @Description //TODO 读取excel用例工具类
* @Date 17:28 2021/6/17
* @Param [rows, cells]行号数组,列号数组
**/
public static Object[][] dataS(int[] rows, int[] cells) {
Object[][] dataS = ExcelUtil.DiscreteDataS(BUNDLE.getString("excelAddress"), BUNDLE.getString("sheet"), rows, cells);
return dataS;
}
其中BUNDLE.getString(“excelAddress”), BUNDLE.getString(“sheet”)是我听过读取配置文件里面我放的Excel文件地址以及文件所在sheet的名称!
2.读取到用例后通过Testng的@DataProvider这个注释来进行参数化
/**
* @return void
* @Author ChengZhiHao
* @Description //TODO admin
* @Date 17:52 2021/6/17
* @Param [parameter, expected]
**/
@Test(dataProvider = "getListGroupCase1", dataProviderClass = ReadParameter.class)
public void getListGroupCase1(String parameter, String expected) {
JSONObject jsonObject = HttpClientUtil.httpPostJson("testUrl", "CrmPriceGetListGroup", parameter);
String code = jsonObject.getString("code");
Assert.assertEquals(code, expected);
}
3.HttpClientUtil是我封装的工具类,用来执行请求
/**
* @return void
* @Author ChengZhiHao
* @Description //TODO admin
* @Date 17:52 2021/6/17
* @Param [parameter, expected]
**/
@Test(dataProvider = "getListGroupCase1", dataProviderClass = ReadParameter.class)
public void getListGroupCase1(String parameter, String expected) {
JSONObject jsonObject = HttpClientUtil.httpPostJson("testUrl", "CrmPriceGetListGroup", parameter);
String code = jsonObject.getString("code");
Assert.assertEquals(code, expected);
}
4.通过Testng执行case生成测试报告
<suite name="测试">
<test name="apitechflowcontroller">
<!--执行case-->
<classes>
<class name="com.qixin.cases.CrmPriceGetListGroup"></class>
</classes>
</test>
<!--生成测试报告-->
<listeners>
<listener class-name="com.qixin.report.ExtentTestReport"/>
</listeners>
</suite>
5.测试报告展示
下面是一些工具类,我创建的是maven项目。
读取Excel工具类:
/**
* 支持传入连续的行连续的列
*
* @param excelPath 文件地址
* @param sheetName sheet名称
* @param startRow 开始行(行号非下标索引)
* @param endRow 结束行(行号非下标索引)
* @param startCell 开始列(列号非下标索引)
* @param endCell 结束列(列号非下标索引)
* @return 读取到的excel数据
*/
public static Object[][] continuousDataS(String excelPath, String sheetName, int startRow, int endRow, int startCell, int endCell) {
/**准备一个二维数组存放数据*/
Object[][] dataS = null;
/**获取文件路径*/
File file = new File(excelPath);
/**获取workBook对象*/
try {
/**存放获取到的数据Object[row][cell]读取到的是几行几列的数据*/
dataS = new Object[endRow - startRow + 1][endCell - startCell + 1];
/**创建工作簿*/
Workbook workbook = WorkbookFactory.create(file);
/**获取sheet对象*/
Sheet sheet = workbook.getSheet(sheetName);
/**获取行*/
for (int i = startRow; i <= endRow; i++) {
Row row = sheet.getRow(i - 1);
/**获取列*/
for (int j = startCell; j <= endCell; j++) {
Cell cell = row.getCell(j - 1, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
/**把值转换成为字符串*/
cell.setCellType(CellType.STRING);
/**获取单元格的值*/
String cellValue = cell.getStringCellValue();
dataS[i - startRow][j - startCell] = cellValue;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return dataS;
}
/**
* 支持传入非连续的行和列
*
* @param excelPath 文件地址
* @param sheetName sheet名称
* @param rows 行号数组
* @param cells 列号数组
* @return 读取到的excel数据
*/
public static Object[][] DiscreteDataS(String excelPath, String sheetName, int[] rows, int[] cells) {
/**String excelPath = "src/main/resources/TestCaseData/v1.xlsx";*/
/**准备一个二维数组存放数据*/
Object[][] dataS = null;
/**获取文件路径*/
File file = new File(excelPath);
/**获取workBook对象*/
//
try {
/**创建工作簿*/
Workbook workbook = WorkbookFactory.create(file);
/**获取sheet对象*/
Sheet sheet = workbook.getSheet(sheetName);
/**定义保存数据的数组*/
dataS = new Object[rows.length][cells.length];
/**获取行*/
for (int i = 0; i < rows.length; i++) {
/**根据行索引取出一行*/
Row row = sheet.getRow(rows[i] - 1);
/**获取列*/
for (int j = 0; j < cells.length; j++) {
Cell cell = row.getCell(cells[j] - 1, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
/**把值转换成为字符串*/
cell.setCellType(CellType.STRING);
/**获取单元格的值*/
String cellValue = cell.getStringCellValue();
dataS[i][j] = cellValue;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return dataS;
}
HttpClient连接池工具类
和Mybatis连接池一样的意思,具体可以自己百度一下
/**
* @PackageName: com.czh.util
* @ClassName: HttpClientConnection
* @Description: HttpClientConnection/description:连接池管理器
* @Author: ChengZhiHao
* @Date: 2021/4/27 14:23
* @Version: v1.0
*/
public class HttpClientConnection {
private static PoolingHttpClientConnectionManager manager;
static {
SSLContextBuilder builder = new SSLContextBuilder();
try {
builder.loadTrustMaterial(null, new TrustSelfSignedStrategy());
SSLConnectionSocketFactory sslSf;
sslSf = new SSLConnectionSocketFactory(builder.build());
// 配置同时支持 HTTP 和 HTTPS
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
.register(AllConstant.HTTP, PlainConnectionSocketFactory.getSocketFactory())
.register(AllConstant.HTTPS, sslSf).build();
manager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
//设置最大连接数
manager.setMaxTotal(AllConstant.MAX_TOTAL);
//设置最大主机连接数
manager.setDefaultMaxPerRoute(AllConstant.DEFAULT_MAX_PER_ROUTE);
} catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
e.printStackTrace();
}
}
/**
* @return org.apache.http.impl.client.CloseableHttpClient
* @Author ChengZhiHao
* @Description //TODO httpClient连接池
* @Date 10:17 2021/5/7
* @Param []
**/
public static CloseableHttpClient getHttpClientConnection() {
//设置超时时间
RequestConfig requestconfig = RequestConfig.custom()
.setConnectTimeout(AllConstant.HTTP_CONNECT_TIME_OUT)
.setConnectionRequestTimeout(AllConstant.HTTP_CONNECTION_REQUEST_TIME_OUT)
.setSocketTimeout(AllConstant.HTTP_SOCKET_TIME_OUT).build();
return HttpClients.custom()
// 设置连接池管理
.setConnectionManager(manager)
.setDefaultRequestConfig(requestconfig)
// 设置重试次数
.setRetryHandler(new DefaultHttpRequestRetryHandler(AllConstant.RETRY_COUNT, false)).build();
}
}
用来执行请求的HttpClientUtil工具类
我封装的方法较多,可以悬着性看一下。
/**
* @PackageName: com.czh.util
* @ClassName: HttpClientUtil
* @Description: HttpClientUtil/description:http请求工具类
* @Author: ChengZhiHao
* @Date: 2021/4/12 14:46
* @Version: v1.0
*/
public class HttpClientUtil {
/**
* 日志打印
*/
private static final Logger log = Logger.getLogger(Test.class);
/**
* 加载application.properties
*/
private static final ResourceBundle BUNDLE = ResourceBundle.getBundle("application", Locale.CHINA);
/**
* @return com.alibaba.fastjson.JSONObject
* @Author ChengZhiHao
* @Description //TODO post请求,Json格式
* @Date 11:09 2021/5/10
* @Param [testUrl, interfaceAddress, data]
**/
public static JSONObject httpPostJson(String testUrl, String interfaceAddress, String data) {
CloseableHttpClient client = HttpClientConnection.getHttpClientConnection();
HttpPost post = new HttpPost(BUNDLE.getString(testUrl) + BUNDLE.getString(interfaceAddress));
log.info(BUNDLE.getString(testUrl) + BUNDLE.getString(interfaceAddress));
post.setHeader("Content-Type", "application/json");
JSONObject parameter = JSONObject.parseObject(data);
post.setEntity(new StringEntity(parameter.toString(), "utf-8"));
CloseableHttpResponse closeableHttpResponse = null;
try {
closeableHttpResponse = client.execute(post);
int code = closeableHttpResponse.getStatusLine().getStatusCode();
if (code == AllConstant.HTTP_CODE_200) {
String result = EntityUtils.toString(closeableHttpResponse.getEntity());
JSONObject resultJsonObject = JSONObject.parseObject(result);
log.info(resultJsonObject);
return resultJsonObject;
} else {
log.info("状态码:" + code + "," + BUNDLE.getString(testUrl) + BUNDLE.getString(interfaceAddress) + "接口请求响应错误");
return null;
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (closeableHttpResponse != null) {
try {
closeableHttpResponse.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return parameter;
}
/**
* @return com.alibaba.fastjson.JSONObject
* @Author ChengZhiHao
* @Description //TODO post请求,form-data格式
* @Date 13:10 2021/5/10
* @Param [testUrl, interfaceAddress, data]
**/
public static JSONObject httpPostFormData(String testUrl, String interfaceAddress, String data) {
CloseableHttpClient client = HttpClientConnection.getHttpClientConnection();
HttpPost post = new HttpPost(BUNDLE.getString(testUrl) + BUNDLE.getString(interfaceAddress));
JSONObject parameter = JSONObject.parseObject(data);
List<BasicNameValuePair> params = new ArrayList<BasicNameValuePair>();
//通过迭代器取出所有的key,再获取每一个键对应的值
Set<String> keySet = parameter.keySet();
for (String key : keySet) {
String value = (String) parameter.get(key);
params.add(new BasicNameValuePair(key, value));
}
CloseableHttpResponse closeableHttpResponse = null;
try {
post.setEntity(new UrlEncodedFormEntity(params));
closeableHttpResponse = client.execute(post);
int code = closeableHttpResponse.getStatusLine().getStatusCode();
if (code == AllConstant.HTTP_CODE_200) {
String result = EntityUtils.toString(closeableHttpResponse.getEntity());
log.info(closeableHttpResponse);
JSONObject resultJsonObject = JSONObject.parseObject(result);
log.info(resultJsonObject);
return resultJsonObject;
} else {
log.info("状态码:" + code + "," + BUNDLE.getString(testUrl) + BUNDLE.getString(interfaceAddress) + "接口请求响应错误");
return null;
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (closeableHttpResponse != null) {
try {
closeableHttpResponse.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return parameter;
}
/**
* @return java.lang.String
* @Author ChengZhiHao
* @Description //TODO get请求
* @Date 14:06 2021/5/10
* @Param [testUrl, interfaceAddress, data]
**/
public static String httpGet(String testUrl, String interfaceAddress, String data) {
CloseableHttpClient client = HttpClientConnection.getHttpClientConnection();
CloseableHttpResponse closeableHttpResponse = null;
try {
URIBuilder build;
build = new URIBuilder(BUNDLE.getString(testUrl) + BUNDLE.getString(interfaceAddress));
JSONObject parameter = JSONObject.parseObject(data);
if (parameter != null) {
Set<String> keys = parameter.keySet();
for (String key : keys) {
String value = (String) parameter.get(key);
//在请求地址上拼接参数
build.setParameter(key, value);
}
}
HttpGet get = new HttpGet(build.build());
closeableHttpResponse = client.execute(get);
int code = closeableHttpResponse.getStatusLine().getStatusCode();
if (code == AllConstant.HTTP_CODE_200) {
String result = EntityUtils.toString(closeableHttpResponse.getEntity());
log.info(closeableHttpResponse);
return result;
} else {
log.info("状态码:" + code + "," + BUNDLE.getString(testUrl) + BUNDLE.getString(interfaceAddress) + "接口请求响应错误");
return null;
}
} catch (URISyntaxException | IOException e) {
e.printStackTrace();
} finally {
if (closeableHttpResponse != null) {
try {
closeableHttpResponse.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
/**
* @return java.lang.String
* @Author ChengZhiHao
* @Description //TODO post请求,form-data格式,重定向
* @Date 14:38 2021/5/10
* @Param [testUrl, interfaceAddress, data]
**/
public static String redirectFormData(String testUrl, String interfaceAddress, String data) {
log.info("我要测试");
CloseableHttpClient client = HttpClientConnection.getHttpClientConnection();
HttpPost post = new HttpPost(BUNDLE.getString(testUrl) + BUNDLE.getString(interfaceAddress));
JSONObject parameter = JSONObject.parseObject(data);
List<BasicNameValuePair> params = new ArrayList<BasicNameValuePair>();
//通过迭代器取出所有的key,再获取每一个键对应的值
Set<String> keySet = parameter.keySet();
for (String key : keySet) {
String value = (String) parameter.get(key);
params.add(new BasicNameValuePair(key, value));
}
CloseableHttpResponse newCloseableHttpResponse = null;
try {
post.setEntity(new UrlEncodedFormEntity(params));
CloseableHttpResponse closeableHttpResponse = client.execute(post);
int code = closeableHttpResponse.getStatusLine().getStatusCode();
String result = EntityUtils.toString(closeableHttpResponse.getEntity(), "UTF-8");
log.info(result);
if (code == AllConstant.HTTP_CODE_302) {
//跳转的目标地址是在 HTTP-HEAD 中的
Header header = closeableHttpResponse.getFirstHeader("location");
System.out.println(header);
//这就是跳转后的地址,再向这个地址发出新申请,以便得到跳转后的信息
String newUrl = header.getValue();
System.out.println(newUrl);
HttpPost newPost = new HttpPost(newUrl);
newCloseableHttpResponse = client.execute(newPost);
String newResult = EntityUtils.toString(newCloseableHttpResponse.getEntity(), "UTF-8");
log.info(newResult);
return newResult;
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (newCloseableHttpResponse != null) {
try {
newCloseableHttpResponse.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
/**
* @return java.lang.String
* @Author ChengZhiHao
* @Description //TODO post请求,Json格式,重定向
* @Date 14:39 2021/5/10
* @Param [testUrl, interfaceAddress, data]
**/
public static String redirectPost(String testUrl, String interfaceAddress, String data) {
CloseableHttpClient client = HttpClientConnection.getHttpClientConnection();
HttpPost post = new HttpPost(BUNDLE.getString(testUrl) + BUNDLE.getString(interfaceAddress));
JSONObject parameter = JSONObject.parseObject(data);
post.setEntity(new StringEntity(parameter.toString(), "utf-8"));
CloseableHttpResponse newCloseableHttpResponse = null;
try {
CloseableHttpResponse closeableHttpResponse = client.execute(post);
int code = closeableHttpResponse.getStatusLine().getStatusCode();
String result = EntityUtils.toString(closeableHttpResponse.getEntity(), "UTF-8");
log.info(result);
if (code == AllConstant.HTTP_CODE_302) {
//跳转的目标地址是在 HTTP-HEAD 中的
Header header = closeableHttpResponse.getFirstHeader("location");
System.out.println(header);
//这就是跳转后的地址,再向这个地址发出新申请,以便得到跳转后的信息
String newUrl = header.getValue();
System.out.println(newUrl);
HttpPost newPost = new HttpPost(newUrl);
newCloseableHttpResponse = client.execute(newPost);
String newResult = EntityUtils.toString(newCloseableHttpResponse.getEntity(), "UTF-8");
log.info(newResult);
return newResult;
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (newCloseableHttpResponse != null) {
try {
newCloseableHttpResponse.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
/**
* @return com.alibaba.fastjson.JSONObject
* @Author ChengZhiHao
* @Description //TODO 文件上传
* @Date 15:19 2021/5/10
* @Param [testUrl, interfaceAddress, path]
**/
public JSONObject uploadFile(String testUrl, String interfaceAddress, String path) {
File file = new File(path);
CloseableHttpClient client = HttpClientConnection.getHttpClientConnection();
HttpPost post = new HttpPost(BUNDLE.getString(testUrl) + BUNDLE.getString(interfaceAddress));
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
builder.addPart("file", new FileBody(file, ContentType.DEFAULT_BINARY));
post.setEntity(builder.build());
CloseableHttpResponse closeableHttpResponse = null;
try {
//执行提交
closeableHttpResponse = client.execute(post);
int code = closeableHttpResponse.getStatusLine().getStatusCode();
if (code == AllConstant.HTTP_CODE_200) {
String result = EntityUtils.toString(closeableHttpResponse.getEntity(), StandardCharsets.UTF_8);
JSONObject jsonObject = JSONObject.parseObject(result);
return jsonObject;
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (closeableHttpResponse != null) {
try {
closeableHttpResponse.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
}
最后希望这些能帮助大家,有什么问题可以留言,后面我会把Mysql以及Mybatis都集成进来,也会上传到githup上。
我计划写个简单的增删改查关联case,然后通过Jenkins集成执行case,把返回的结果写入数据库中,大致就这样吧。