java接口自动化Excel占位符_基于maven+java+TestNG+httpclient+poi+jsonpath+ExtentReport的接口自动化测试框架...

接口自动化框架

项目说明

本框架是一套基于maven+java+TestNG+httpclient+poi+jsonpath+ExtentReport而设计的数据驱动接口自动化测试框架,TestNG 作为执行器,poi用于读取存放于excel的接口用例,jsonPath用于校验返回值,以及提取返回值。本框架无需你使用代码编写用例,在excel中即可进行接口用例编写,接口依赖关联,接口断言,控制用例的运行。

技术栈

maven

java

TestNG

httpclient

poi

jsonpath

ExtentReport

环境部署

安装jdk8,并配置好环境变量

maven中直接导入项目工程包,导入成功后,maven会自动下载当前项目的所有依赖包

代码设计与功能说明

1、定义运行配置文件 api-config.xml

api请求根路径、请求头及初始化参数值可以在api-config上进行配置。

rootUrl: 必须的配置,api的根路径,在调用api时用于拼接,配置后,会在自动添加到用例中的url的前缀中。

headers: 非必须配置,配置后在调用api时会将对应的name:value值设置到所有请求的请求头中header-name:header-value。

params:非必须配置,公共参数,通常放置初始化配置数据,所有用例执行前,会将params下所有的param配置进行读取并存储到公共参数池中,在用例执行时,使用特定的关键字(${param_name})可以获取。具体如下:

api-config.xml配置信息

http://127.0.0.1:12306

接口自动化测试报告demo

2、测试用例的设计

测试用例以excel格式的文件保存,除表头外,一行代表一个api用例。执行时会依次从左到右,从上到下执行。case/api-data.xls测试用例的数据格式如下:

b154f4a0c7ea55bb1471642a38ade4e6.png

run:标记为‘Y’时,该行数据会被读取执行;标记为‘N’则不被执行

description:该用例描述,在报告中体现。

method:该api测试用例的请求方法。

url:该api测试用例的请求路径。

说明:

若配置文件(api-config.xml)中rootUrl为:http://127.0.0.1:12306 ,url的值为:/parkinside ,框架执行的时候会根据配置文件中rootUrl进行自动拼接为:http://127.0.0.1:12306/parkinside 。若url填写为http作为前缀的值如:http://127.0.0.1:12306/parkinside 将不会进行拼接。

param:请求方法为post时,body的内容(暂只支持json,不支持xml)

verify:对于api请求response数据的验证(可使用jsonPath进行校验)。校验多个使用“;”进行隔开。

若verify填写值为:$.username=wuya;$.userID=22 ,则会校验返回值中$.username的值为wuya,$.userID的值为22,只要有一个校验错误,后面的其他校验项将停止校验。

save:使用jsonPath对response的数据进行提取存储。

说明:若save值为:id=$.userId;age=$.age ,接口实际返回内容为:{"username":"chenwx","userId":"1000","age":"18"},则接口执行完成后,会将公共参数userId的值存储为1000,age存储为18。公共参数可在后面的用例中进行使用。

公共关联池中的公共参数使用

测试用例excel表中可以使用‘${param_name}’占位符,在执行过程中如果判断含有占位符,则会将该值替换为公共参数里面的值,如果找不到将会报错。具体使用格式如下:

{

"token":"${g_token}",

"vpl":"AJ3585"

}

3、函数助手

测试用例excel表中可以使用‘__funcName(args)’占位符,在执行过程中如果判断含有该占位符,且funcName存在,则会执行相应的函数后进行替换。部分函数说明如下:

__random(param1,param2):随机生成一个定长的字符串(不含中文)。param1:长度(非必填,默认为6),param2:纯数字标识(非必填,默认为false)。

__randomText(param1): 随机生成一个定长的字符串(含中文)。param1:长度(非必填,默认为6)

__date(param1): 生成执行该函数时的时间格式化字符串。param1为转换的格式,默认为生成当前13位时间戳。

具体使用格式如下:

{

"drivers":"张三",

"cmsuer":"__random(8,false)",

"time":"__date()"

}

函数random执行时会产生8位长度的随机字符串,并传给变量cmsuer;函数date在执行时,会产生一个13位的时间戳,并传给变量time。

4、测试执行主程序

package test.com.sen.api;

import com.alibaba.fastjson.JSON;

import com.sen.api.beans.ApiDataBean;

import com.sen.api.configs.ApiConfig;

import com.sen.api.excepions.ErrorRespStatusException;

import com.sen.api.listeners.AutoTestListener;

import com.sen.api.listeners.RetryListener;

import com.sen.api.utils.*;

import org.apache.http.Header;

import org.apache.http.HttpEntity;

import org.apache.http.HttpResponse;

import org.apache.http.client.HttpClient;

import org.apache.http.client.methods.*;

import org.apache.http.entity.StringEntity;

import org.apache.http.entity.mime.MultipartEntity;

import org.apache.http.entity.mime.content.FileBody;

import org.apache.http.entity.mime.content.StringBody;

import org.apache.http.message.BasicHeader;

import org.apache.http.params.CoreConnectionPNames;

import org.apache.http.util.EntityUtils;

import org.dom4j.DocumentException;

import org.testng.Assert;

import org.testng.ITestContext;

import org.testng.annotations.*;

import org.testng.annotations.Optional;

import java.io.File;

import java.io.InputStream;

import java.io.UnsupportedEncodingException;

import java.nio.file.Paths;

import java.util.*;

import java.util.regex.Matcher;

@Listeners({ AutoTestListener.class, RetryListener.class })

public class ApiTest extends TestBase {

/**

* api请求跟路径

*/

private static String rootUrl;

/**

* 跟路径是否以‘/’结尾

*/

private static boolean rooUrlEndWithSlash = false;

/**

* 所有公共header,会在发送请求的时候添加到http header上

*/

private static Header[] publicHeaders;

/**

* 是否使用form-data传参 会在post与put方法封装请求参数用到

*/

private static boolean requestByFormData = false;

/**

* 配置

*/

private static ApiConfig apiConfig;

/**

* 所有api测试用例数据

*/

protected List dataList = new ArrayList();

private static HttpClient client;

/**

* 初始化测试数据

*

* @throws Exception

*/

@Parameters("envName")

@BeforeSuite

public void init(@Optional("api-config.xml") String envName) throws Exception {

String configFilePath = Paths.get(System.getProperty("user.dir"), envName).toString();

ReportUtil.log("api config path:" + configFilePath);

apiConfig = new ApiConfig(configFilePath);

// 获取基础数据

rootUrl = apiConfig.getRootUrl();

rooUrlEndWithSlash = rootUrl.endsWith("/");

Map params = apiConfig.getParams();

setSaveDates(params);

List headers = new ArrayList();

apiConfig.getHeaders().forEach((key, value) -> {

Header header = new BasicHeader(key, value);

if(!requestByFormData && key.equalsIgnoreCase("content-type") && value.toLowerCase().contains("form-data")){

requestByFormData=true;

}

headers.add(header);

});

publicHeaders = headers.toArray(new Header[headers.size()]);

client = new SSLClient();

client.getParams().setParameter(

CoreConnectionPNames.CONNECTION_TIMEOUT, 60000); // 请求超时

client.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, 60000); // 读取超时

}

@Parameters({ "excelPath", "sheetName" })

@BeforeTest

public void readData(@Optional("case/api-data.xls") String excelPath, @Optional("Sheet1") String sheetName) throws DocumentException {

dataList = readExcelData(ApiDataBean.class, excelPath.split(";"),

sheetName.split(";"));

}

/**

* 过滤数据,run标记为Y的执行。

*

* @return

* @throws DocumentException

*/

@DataProvider(name = "apiDatas")

public Iterator getApiData(ITestContext context)

throws DocumentException {

List dataProvider = new ArrayList();

for (ApiDataBean data : dataList) {

//poi解析处理Excel表时,若单元格中的布尔值为Y或者true,则解析到的布尔值为true;若单元格中的布尔值为false或者其他值或者为空,则解析到的布尔值为false

if (data.isRun()) {

dataProvider.add(new Object[] { data });

}

}

return dataProvider.iterator();

}

@Test(dataProvider = "apiDatas")

public void apiTest(ApiDataBean apiDataBean) throws Exception {

ReportUtil.log("--- test start ---");

if (apiDataBean.getSleep() > 0) {

// sleep休眠时间大于0的情况下进行暂停休眠

ReportUtil.log(String.format("sleep %s seconds",

apiDataBean.getSleep()));

Thread.sleep(apiDataBean.getSleep() * 1000);

}

String apiParam = buildRequestParam(apiDataBean);

// 封装请求方法

HttpUriRequest method = parseHttpRequest(apiDataBean.getUrl(),

apiDataBean.getMethod(), apiParam);

String responseData;

try {

// 执行

HttpResponse response = client.execute(method);

int responseStatus = response.getStatusLine().getStatusCode();

ReportUtil.log("返回状态码:"+responseStatus);

if (apiDataBean.getStatus()!= 0) {

Assert.assertEquals(responseStatus, apiDataBean.getStatus(),

"返回状态码与预期不符合!");

}

HttpEntity respEntity = response.getEntity();

Header respContentType = response.getFirstHeader("Content-Type");

if (respContentType != null && respContentType.getValue() != null

&& (respContentType.getValue().contains("download") || respContentType.getValue().contains("octet-stream"))) {

String conDisposition = response.getFirstHeader(

"Content-disposition").getValue();

String fileType = conDisposition.substring(

conDisposition.lastIndexOf("."),

conDisposition.length());

String filePath = "download/" + RandomUtil.getRandom(8, false)

+ fileType;

InputStream is = response.getEntity().getContent();

Assert.assertTrue(FileUtil.writeFile(is, filePath), "下载文件失败。");

// 将下载文件的路径放到{"filePath":"xxxxx"}进行返回

responseData = "{\"filePath\":\"" + filePath + "\"}";

} else {

responseData=EntityUtils.toString(respEntity, "UTF-8");

}

} catch (Exception e) {

throw e;

} finally {

method.abort();

}

// 输出返回数据log

ReportUtil.log("resp:" + responseData);

// 验证预期信息

verifyResult(responseData, apiDataBean.getVerify(),

apiDataBean.isContains());

// 对返回结果进行提取保存。

saveResult(responseData, apiDataBean.getSave());

}

private String buildRequestParam(ApiDataBean apiDataBean) {

// 分析处理预参数 (函数生成的参数)

String preParam = buildParam(apiDataBean.getPreParam());

savePreParam(preParam);// 保存预存参数 用于后面接口参数中使用和接口返回验证中

// 处理参数

String apiParam = buildParam(apiDataBean.getParam());

return apiParam;

}

/**

* 封装请求方法

*

* @param url

* 请求路径

* @param method

* 请求方法

* @param param

* 请求参数

* @return 请求方法

* @throws UnsupportedEncodingException

*/

private HttpUriRequest parseHttpRequest(String url, String method, String param) throws UnsupportedEncodingException {

// 处理url

url = parseUrl(url);

ReportUtil.log("method:" + method);

ReportUtil.log("url:" + url);

ReportUtil.log("param:" + param.replace("\r\n", "").replace("\n", ""));

//upload表示上传,也是使用post进行请求

if ("post".equalsIgnoreCase(method) || "upload".equalsIgnoreCase(method)) {

// 封装post方法

HttpPost postMethod = new HttpPost(url);

postMethod.setHeaders(publicHeaders);

//如果请求头的content-type的值包含form-data 或者 请求方法为upload(上传)时采用MultipartEntity形式

HttpEntity entity = parseEntity(param,requestByFormData || "upload".equalsIgnoreCase(method));

postMethod.setEntity(entity);

return postMethod;

} else if ("put".equalsIgnoreCase(method)) {

// 封装put方法

HttpPut putMethod = new HttpPut(url);

putMethod.setHeaders(publicHeaders);

HttpEntity entity = parseEntity(param,requestByFormData );

putMethod.setEntity(entity);

return putMethod;

} else if ("delete".equalsIgnoreCase(method)) {

// 封装delete方法

HttpDelete deleteMethod = new HttpDelete(url);

deleteMethod.setHeaders(publicHeaders);

return deleteMethod;

} else {

// 封装get方法

HttpGet getMethod = new HttpGet(url);

getMethod.setHeaders(publicHeaders);

return getMethod;

}

}

/**

* 格式化url,替换路径参数等。

*

* @param shortUrl

* @return

*/

private String parseUrl(String shortUrl) {

// 替换url中的参数

shortUrl = getCommonParam(shortUrl);

if (shortUrl.startsWith("http")) {

return shortUrl;

}

if (rooUrlEndWithSlash == shortUrl.startsWith("/")) {

if (rooUrlEndWithSlash) {

shortUrl = shortUrl.replaceFirst("/", "");

} else {

shortUrl = "/" + shortUrl;

}

}

return rootUrl + shortUrl;

}

/**

* 格式化参数,如果是from-data格式则将参数封装到MultipartEntity否则封装到StringEntity

* @param param 参数

* @param formData 是否使用form-data格式

* @return

* @throws UnsupportedEncodingException

*/

private HttpEntity parseEntity(String param,boolean formData) throws UnsupportedEncodingException{

if(formData){

Map paramMap = JSON.parseObject(param,

HashMap.class);

MultipartEntity multiEntity = new MultipartEntity();

for (String key : paramMap.keySet()) {

String value = paramMap.get(key);

Matcher m = funPattern.matcher(value);

if (m.matches() && m.group(1).equals("bodyfile")) {

value = m.group(2);

multiEntity.addPart(key, new FileBody(new File(value)));

} else {

multiEntity.addPart(key, new StringBody(paramMap.get(key)));

}

}

return multiEntity;

}else{

return new StringEntity(param, "UTF-8");

}

}

}

5、测试总执行器testng.xml(收集测试用例,批量执行并生成测试报告)

6、测试运行方式

IDEA工具直接执行testng.xml(以testng形式运行)即可(IDEA工具需要先装好testng插件)

maven执行:根目录下,执行 mvn test

7、测试报告呈现

testng.xml执行可视化报告:${workspace}/test-output/index.html

maven执行报告:${workspace}/target/test-output/index.html

22b38367ae36302f133af64be5548717.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/337883.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言为什么要定义short,int,long这么多整数类型?

点击上方蓝字关注我,了解更多咨询整数类型有int、short int、long int三种类型,用于需要不同存储空间的整数使用。整数类型有正整数和负整数之分,在C语言中,规定整型的最高位为符号位,最高位为“0”表示正数&#xff0…

c语言中函数参数类型的探究

点击上方蓝字关注我,了解更多咨询函数中的参数个数可以是0,也可以是一个或多个参数。下面我们带着这三种不同的情况,分别在c语言中进行讨论。1、函数没有参数,表示没有参数列表。int func1(); //声明一个函数,该函数…

c语言野指针产生的原因

点击上方蓝字关注我,了解更多咨询1、指针变量未初始化,任何指针变量刚被创建时不会自动成为 NULL 指针,它的缺省值是随机的。所以,指针变量在创建的同时应当被初始化,要么将指针设置为 NULL ,要么让它指向合…

c语言中assert函数的使用注意

点击上方蓝字关注我,了解更多咨询1、使用assert检查函数开始时传入参数的合法性。2、每个assert只检验一个条件,因为在同时检验多个条件时,如果断言失败,就无法直观地判断哪个条件失败。实例int resetBufferSize(int nNewSize) {/…

es 调整gc_实际中进行GC调整

es 调整gc调优垃圾回收与任何其他性能调优活动没有什么不同。 您需要确保您了解当前的情况和所需的结果,而不是因为对应用程序的随机部分进行调整而产生了诱惑。 通常,只需执行以下过程即可: 陈述您的绩效目标 运行测试 测量 与目标比较…

c语言中assert函数是什么

点击上方蓝字关注我,了解更多咨询1、assert将通过检查表达式expresion的值来决定是否需要终止执行程序。2、如果表达式expresion的值是假的(即0),它将首先向标准错误流stderr打印错误信息。通过调用abort函数终止程序运行。否则,assert没有效…

sci检索没有馆藏号_转变馆藏

sci检索没有馆藏号您是否曾经想替换过HashSet或HashMap使用的equals和hashCode方法? 或者有一个List的一些元素类型伪装成的List相关类型的? 转换集合使这成为可能,并且本文将展示如何实现。 总览 转换集合是LibFX 0.3.0的一项功能&#xf…

c语言中exit和return的区别

点击上方蓝字关注我,了解更多咨询1、返回函数值的return是关键字,exit是一个函数。return是语言级的,它表示调用堆栈的返回;exit是系统调用级的,它表示一个过程的结束。2、return是函数的退出,exit是进程的…

java怎样返回json_java怎么返回json

详细内容本篇文章将介绍如何编写Java程序来返回Json数据,本次以三个方式进行介绍。推荐课程:Java教程,方式1:当然是手敲所有代码,来进行json数据的返回。需要 HttpHttpServletRequest request HttpServletResponse res…

c语言中realloc函数是什么

点击上方蓝字关注我,了解更多咨询1、判断当前指针是否有足够的连续空间。如果足够,扩大ptr指向的地址并返回。如果不够,如何根据size指定的大小分配空间,将原始数据复制到新分配的内存中,然后释放原始ptr指向的区域。2…

java jsp学习指南_JSP教程–最终指南

java jsp学习指南编者注: JavaServer Pages(JSP)技术使您可以轻松创建同时包含静态和动态组件的Web内容。 JSP技术提供了Java Servlet技术的所有动态功能,但提供了一种更自然的方法来创建静态内容。 JSP技术的主要功能包括用于开…

c语言中main函数是什么

点击上方蓝字关注我,了解更多咨询1、main函数是C程序的入口函数,即程序的执行从main函数开始,其他函数的调动也直接或间接地在main函数中调用。2、main函数的返回值用于解释程序的退出状态。若返回0,则表示程序正常退出。返回其他…

c语言中__cplusplus是什么

点击上方蓝字关注我,了解更多咨询1、__cplusplus和extern“C”一般都是配对使用,如果定义了__cplusplus(cpp文件默认定义了该宏),则采用C语言方式进行编译。2、是在C中特有的,__cplusplus 其实就是C。实例#ifndef __CODERSRC_H__ …

c语言中fwirte函数的使用方法示例

点击上方蓝字关注我,了解更多咨询1、fwrite函数用于将缓冲区数据写入文件,并返回成功写入文件的元素数。如果出现错误或到达文件末尾,可能小于nmemb。2、fwrite函数不区分文件的尾部和错误,因此调用者必须使用feof和ferror来判断发…

联通光纤限制连接数_从数百万个光纤(而不是数千个线程)中查询数据库

联通光纤限制连接数jOOQ是在Java中执行SQL的好方法,而Quasar光纤带来了大大提高的并发性 我们很高兴在平行世界的 Fabio Tudone的jOOQ博客上宣布另一个非常有趣的来宾帖子。 Parallel Universe开发了一个开放源代码堆栈,使开发人员可以轻松地在JVM上对…

java 工程新建ivy文件_Hadoop学习之路(八)在eclispe上搭建Hadoop开发环境

一、添加插件将hadoop-eclipse-plugin-2.7.5.jar放入eclipse的plugins文件夹中二、在Windows上安装Hadoop2.7.5版本最好与Linux集群中的hadoop版本保持一致1、将hadoop-2.7.5-centos-6.7.tar.gz解压到Windows上的C盘software目录中2、配置hadoop的环境变量HADOOP_HOMEC:\softwa…

c语言中fclose函数的使用你会吗

点击上方蓝字关注我&#xff0c;了解更多咨询1、C语言fclose函数用于关闭使用fopen成功打开的文件。2、fopen函数和fclose函数总是成对出现。如果文件成功打开fopen函数&#xff0c;请使用fclose函数关闭文件。实例#include <stdio.h> #include <stdlib.h>int main…

Java反序列化json内存溢出_fastJson与一起堆内存溢出'血案'

现象QA同学反映登录不上服务器排查问题1–日志级别查看log,发现玩家登录的时候抛出了一个java.lang.OutOfMemoryError大概代码是向Redis序列化一个PlayerMirror镜像数据,但是在JSON.toJSONString的时候出现了错误.比较清晰&#xff0c;即序列化的时候expandCapacity,内存不足。…

c语言中预处理器是什么

点击上方蓝字关注我&#xff0c;了解更多咨询1、C语言有预处理器&#xff0c;Java中没有这个概念&#xff0c;其实只是文本替换工具。2、C的预处理器&#xff0c;即CPP&#xff0c;将在实际编译器中完成处理&#xff0c;所有预处理命令将从#开始。实例#include <stdio.h>…

c语言strcat_s函数如何使用

点击上方蓝字关注我&#xff0c;了解更多咨询1、strcat_s函数将strSource指向的字符串添加到其它字符串结尾。因此需要确保strDestination有足够的内存空间来容纳strSource和strDestination两个字符串&#xff0c;否则会导致溢出错误。2、strDestination末端的\0将被覆盖。strS…