- 1.使用Java开展接口自动化需要准备的pom:
- 2.下面开始封装Get和Post请求:
- 3.下面开始准备测试数据:
- 4.测试结果展示,需要修改或添加如下配置:
1.使用Java开展接口自动化需要准备的pom:
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.10</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.14.3</version>
<scope>test</scope>
</dependency>
2.下面开始封装Get和Post请求:
2.1因为使用到了log4j加日志,以及将参数转为JSON格式,下面是我们需要的pom依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
2.2log4j.properties配置文件详情(需要注意:log4j.properties文件需要放resources目录下,不然运行时会找不到文件不能初始化):
2.2(1)可能会有小伙伴想问为什么要加日志?
解答:1.在代码中生成日志有助于我们复盘,运行代码时,记录关键的数据。
2.实时查看代码的运行状态,如果运行已结束,错过运行时监管,事后通过log文件查看重要的运行数据及状态。
#根logger设置
log4j.rootLogger = INFO,console,file
###输出信息到控制台###
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern = [%p] %d{yyyy-MM-dd HH:mm:ss} method: %l -> %m%n
###输出 INFO 级别以上日志文件设置###
log4j.appender.file = org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.File = target/apitest.log
log4j.appender.file.Append = true
log4j.appender.file.layout = org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} method: %l - [ %p ] -> %m%n
Get:
/**
*get请求
* @param url 接口请求地址
* @param params 请求参数
* @return
*/
public static String doGet(String url, Map <String, Object> params){
log.info("开始调用doGet(),此方法用于发起get请求");
int number = 1;
for (Map.Entry<String ,Object> entry : params.entrySet()) {
//如果number=1,那就代表再url与参数的拼接处,以"?"拼接
url += (number == 1) ? ("?" + entry.getKey() + "=" + entry.getValue()) : ("&" + entry.getKey() + "=" + entry.getValue());
log.info("当前请求url和params:" + url);
number++;
}
//指定接口请求方式
HttpGet httpGet = new HttpGet(url);
//设置请求头
httpGet.addHeader("Content-Type","application/x-www-form-urlencoded;charset=UTF-8");
addCookieInRequestHeaderBeforeRequest(httpGet);
CloseableHttpResponse response = null;
CloseableHttpClient client = null;
String result = "";
try {
//发送请求获取响应数据实体
client = HttpClients.createDefault();
//将获取到得数据,添加到请求头
addCookieInRequestHeaderBeforeRequest(httpGet);
response = client.execute(httpGet);
//获取http状态码
int statusCode = response.getStatusLine().getStatusCode();
if (!("200".equals(String.valueOf(statusCode)))) {
log.warn("请求失败,请检查请求参数及请求头!");
}
//获取http响应实体
result = EntityUtils.toString(response.getEntity());
log.info("状态码[" + statusCode + "],响应报文[" + result + "]");
//如果result中有有token,就添加到map
getResultToken(result);
} catch (Exception e) {
log.error("出现传输异常,请检查当前流!");
} finally {
//资源关闭
try {
if (client != null) {
log.info("关闭当前client连接");
client.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (response != null) {
log.info("关闭当前response连接");
response.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
Post:
/**
* post请求
* @param url 接口请求地址
* @param params 接口请求参数
* @return
*/
@RequestWrapper()
public static String doPost(String url, Map<String, Object> params) {
log.info("开始调用doPost(),此方法用于发起Post请求");
//指定请求方式
HttpPost httpPost = new HttpPost(url);
//准备测试数据
List<BasicNameValuePair> paramenters = new ArrayList<>();
//指定请求头
httpPost.addHeader("User-agent","Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.66 Safari/537.36");
httpPost.addHeader("Content-Type","application/x-www-form-urlencoded;charset=UTF-8");
//根据获取得数据,添加请求头(方法将在下面以代码块形式贴出)
addCookieInRequestHeaderBeforeRequest(httpPost);
//1.为什么是使用Object而不是String?
//因为我们要处理的参数肯定不是一种类型。
if (params != null) {
for (Map.Entry<String, Object> entry : params.entrySet()) {
paramenters.add(new BasicNameValuePair(entry.getKey(),entry.getValue() + ""));
}
}
//将参数转为json格式
//JSONObject jsonObject = new JSONObject(params);
//将参数编码格式设置为UTF-8
//StringEntity stringEntity = new StringEntity(jsonObject.toString(),"UTF-8");
CloseableHttpResponse response = null;
CloseableHttpClient client = null;
String result = null;
try {
//httpPost.setEntity(stringEntity);
httpPost.setEntity(new UrlEncodedFormEntity(paramenters, "UTF-8"));
client = HttpClients.createDefault();
//发起请求,获取接口响应信息(状态码,响应报文,或某些特殊的响应头数据)
response = client.execute(httpPost);
log.info("请求url:" + url);
log.info("请求参数:" + jsonObject);
//判断请求头中是否带有Set-cookie
getAndStoreCookiesFromResponseHeader(response);
//获取状态码
int statusCode = response.getStatusLine().getStatusCode();
//获取响应体
result = EntityUtils.toString(response.getEntity());
log.info("状态码[" + statusCode + "],响应报文[" + result + "]");
//判断result中的返回结果是否带有token,如果有添加到map保存(方法将在下面以代码块形式贴出)
getResultToken(result);
if (!("200".equals(String.valueOf(statusCode)))){
log.warn("请求失败,当前状态码:" + statusCode);
log.warn("请求参数:" + jsonObject);
}
} catch (Exception e) {
log.error("运行时异常,报错内容:" + e);
} finally {
//资源关闭
try {
if (client != null) {
log.info("关闭当前client连接");
client.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (response != null) {
log.info("关闭当前response连接");
response.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
//返回响应结果
return result;
}
2.2代码中调用了从result获取token的方法,并将获取到的token添加到请求头。
2.2.1getResultToken():需要提前声明一个map用于保存获取的token
public static Map<String, String> token = new HashMap<>(); //储存token的集合
/**
* 拆分获取response的返回结果,将token添加到map
* @param result Http请求返回result结果
*/
public static void getResultToken(String result) {
log.info("开始调用getResultValueLoad(),此方法用于获取result中的token");
//将result转为JSONObject
JSONObject jsonObject = JSONObject.parseObject(result);
try {
//取出key为data的数据
Object data = jsonObject.get("data");
//data不为空则转型为map
if (data != null && data.toString().trim().length() > 0) {
//getStringToMap():用于String转map,以代码块形式在下面贴出
Map<String, String> stringToMap = getStringToMap(String.valueOf(data));
for (String name : stringToMap.keySet()) {
String value = stringToMap.get(name);
if ("\"token\"".equalsIgnoreCase(name)) {
log.info("将当前token:" + value + "并添加到map储存");
token.put(name, value);
}
}
}
//如果result中没有包含data实体,调用data.toString().trim().length(),可能会引发空指针,所以使用了try/catch
}catch (NullPointerException e) {
log.error("空指针异常,请检查result中是否包含data",e);
}
}
2.2.2getStringToMap():此方法用于String转map
/**
* String转map
* @param str
* @return
*/
public static Map<String,String> getStringToMap(String str){
log.info("开始调用getStringToMap(),此方法用于String转map");
Map<String,String> map = null;
if (str != null && str.trim().length() > 0) {
//根据逗号截取字符串数组
String[] str1 = str.split(",");
//创建Map对象
map = new HashMap<>();
//循环加入map集合
for (int i = 0; i < str1.length; i++) {
//根据":"截取字符串数组
try {
String[] str2 = str1[i].split(":");
//str2[0]为KEY,str2[1]为值
map.put(str2[0], str2[1]);
} catch (ArrayIndexOutOfBoundsException e) {
log.error("请检查传入参数:"+str+",出现索引越界!");
return null;
}
}
}
return map;
}
2.2.3addCookieInRequestHeaderBeforeRequest():如果map中储存有token,则添加至请求头
/**
* 判断map中是否存有值,如果不为空设置为请求头
* @param request http请求返回得结果
*/
private static void addCookieInRequestHeaderBeforeRequest(HttpRequest request) {
log.info("开始调用addCookieInRequestHeaderBeforeRequest(),此方法用于添加请求头");
String getToken = HttpUtil.token.get("token");
if (getToken != null) {
log.info("添加token到请求头");
request.addHeader("token",getToken);
}
}
3.下面开始准备测试数据:
3.1编写测试用例,如下:
@Test
public void test_GetList() {
String url = "http://hd215.api.yesapi.cn/?s=App.User.GetList";
Map<String, Object> params = new HashMap<>();
//这里的key不方便展示,有需要的同学可以自行去官网获取
params.put("app_key","********");
params.put("page",1);
params.put("perpage",20);
doPost(url,params);
}
4.测试结果展示,需要修改或添加如下配置:
4.1TestNG原生reportNG测试报告:
4.1.1需要在idea修改Run—>Edit Configurations—>TestNGListenrs勾选Use default reporters选项:
4.1.2需要的pom:
<dependencies>
<!--testNG报告依赖包-->
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.14.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- 添加reportNG依赖 -->
<dependency>
<groupId>org.uncommons</groupId>
<artifactId>reportng</artifactId>
<version>1.1.2</version>
<scope>test</scope>
<!-- 排除testNG依赖 -->
<exclusions>
<exclusion>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>3.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<!--Test.xml路径-->
<properties>
<xmlFileName>test.xml</xmlFileName>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19</version>
<configuration>
<skipTests>false</skipTests>
<suiteXmlFiles>
<suiteXmlFile>${xmlFileName}</suiteXmlFile>
</suiteXmlFiles>
<properties>
<property>
<name>usedefaultlisteners</name>
<value>false</value>
</property>
<property>
<name>listener</name>
<value>org.uncommons.reportng.HTMLReporter,org.uncommons.reportng.JUnitXMLReporter</value>
</property>
</properties>
<forkMode>always</forkMode>
</configuration>
</plugin>
</plugins>
</build>
</project>
4.1.2配置xml(可以通过xml执行多条测试用例):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="All Test Suite" preserve-order="true" allow-return-values="true">
<listeners>
//reportng需要指定监听器路径
<listener class-name="org.uncommons.reportng.HTMLReporter"/>
<listener class-name="org.uncommons.reportng.JUnitXMLReporter"/>
</listeners>
<test name="查询会员分组">
<classes>
<class name="com.autotest.cases.UserGetListTest"></class>
</classes>
</test>
</suite>
4.1.3最后在项目中找到test-output—>html—>index.html,鼠标右击选择Open in Browser,用浏览器打开我们的测试报告(因为我这里使用了TestNG注解:@DataProvider数据提供者,运行了所有继承类所以抛出错误没有提供数据,大家可以忽略跳过这里):
4.2生成ExtentIReporter版美化测试报告:
4.2.1需要一个ExtentTestNGIReporterListene类,代码如下:
package com.autotest.config;
import com.aventstack.extentreports.ExtentReports;
import com.aventstack.extentreports.ExtentTest;
import com.aventstack.extentreports.ResourceCDN;
import com.aventstack.extentreports.Status;
import com.aventstack.extentreports.model.TestAttribute;
import com.aventstack.extentreports.reporter.ExtentHtmlReporter;
import com.aventstack.extentreports.reporter.configuration.ChartLocation;
import com.aventstack.extentreports.reporter.configuration.Theme;
import org.testng.*;
import org.testng.xml.XmlSuite;
import java.io.File;
import java.util.*;
public class ExtentTestNGIReporterListener implements IReporter {
//生成的路径以及文件名
private static final String OUTPUT_FOLDER = "test-output/";
private static final String FILE_NAME = "apiAutoTest_report.html";
private ExtentReports extent;
@Override
public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites, String outputDirectory) {
init();
boolean createSuiteNode = false;
if(suites.size()>1){
createSuiteNode=true;
}
for (ISuite suite : suites) {
Map<String, ISuiteResult> result = suite.getResults();
//如果suite里面没有任何用例,直接跳过,不在报告里生成
if(result.size()==0){
continue;
}
//统计suite下的成功、失败、跳过的总用例数
int suiteFailSize=0;
int suitePassSize=0;
int suiteSkipSize=0;
ExtentTest suiteTest=null;
//存在多个suite的情况下,在报告中将同一个一个suite的测试结果归为一类,创建一级节点。
if(createSuiteNode){
suiteTest = extent.createTest(suite.getName()).assignCategory(suite.getName());
}
boolean createSuiteResultNode = false;
if(result.size()>1){
createSuiteResultNode=true;
}
for (ISuiteResult r : result.values()) {
ExtentTest resultNode;
ITestContext context = r.getTestContext();
if(createSuiteResultNode){
//没有创建suite的情况下,将在SuiteResult的创建为一级节点,否则创建为suite的一个子节点。
if( null == suiteTest){
resultNode = extent.createTest(r.getTestContext().getName());
}else{
resultNode = suiteTest.createNode(r.getTestContext().getName());
}
}else{
resultNode = suiteTest;
}
if(resultNode != null){
resultNode.getModel().setName(suite.getName()+" : "+r.getTestContext().getName());
if(resultNode.getModel().hasCategory()){
resultNode.assignCategory(r.getTestContext().getName());
}else{
resultNode.assignCategory(suite.getName(),r.getTestContext().getName());
}
resultNode.getModel().setStartTime(r.getTestContext().getStartDate());
resultNode.getModel().setEndTime(r.getTestContext().getEndDate());
//统计SuiteResult下的数据
int passSize = r.getTestContext().getPassedTests().size();
int failSize = r.getTestContext().getFailedTests().size();
int skipSize = r.getTestContext().getSkippedTests().size();
suitePassSize += passSize;
suiteFailSize += failSize;
suiteSkipSize += skipSize;
if(failSize>0){
resultNode.getModel().setStatus(Status.FAIL);
}
resultNode.getModel().setDescription(String.format("Pass: %s ; Fail: %s ; Skip: %s ;",passSize,failSize,skipSize));
}
buildTestNodes(resultNode,context.getFailedTests(), Status.FAIL);
buildTestNodes(resultNode,context.getSkippedTests(), Status.SKIP);
buildTestNodes(resultNode,context.getPassedTests(), Status.PASS);
}
if(suiteTest!= null){
suiteTest.getModel().setDescription(String.format("Pass: %s ; Fail: %s ; Skip: %s ;",suitePassSize,suiteFailSize,suiteSkipSize));
if(suiteFailSize>0){
suiteTest.getModel().setStatus(Status.FAIL);
}
}
}
// for (String s : Reporter.getOutput()) {
// extent.setTestRunnerOutput(s);
// }
extent.flush();
}
private void init() {
//文件夹不存在的话进行创建
File reportDir= new File(OUTPUT_FOLDER);
if(!reportDir.exists()&& !reportDir .isDirectory()){
reportDir.mkdir();
}
ExtentHtmlReporter htmlReporter = new ExtentHtmlReporter(OUTPUT_FOLDER + FILE_NAME);
// 设置静态文件的DNS
//怎么样解决cdn.rawgit.com访问不了的情况
htmlReporter.config().setResourceCDN(ResourceCDN.EXTENTREPORTS);
htmlReporter.config().setDocumentTitle("接口自动化测试报告");
htmlReporter.config().setReportName("Kinfe-apiAutoTest_report");
htmlReporter.config().setChartVisibilityOnOpen(true);
htmlReporter.config().setTestViewChartLocation(ChartLocation.TOP);
htmlReporter.config().setTheme(Theme.STANDARD);
htmlReporter.config().setCSS(".node.level-1 ul{ display:none;} .node.level-1.active ul{display:block;}");
extent = new ExtentReports();
extent.attachReporter(htmlReporter);
extent.setReportUsesManualConfiguration(true);
}
private void buildTestNodes(ExtentTest extenttest, IResultMap tests, Status status) {
//存在父节点时,获取父节点的标签
String[] categories=new String[0];
if(extenttest != null ){
List<TestAttribute> categoryList = extenttest.getModel().getCategoryContext().getAll();
categories = new String[categoryList.size()];
for(int index=0;index<categoryList.size();index++){
categories[index] = categoryList.get(index).getName();
}
}
ExtentTest test;
if (tests.size() > 0) {
//调整用例排序,按时间排序
Set<ITestResult> treeSet = new TreeSet<ITestResult>(new Comparator<ITestResult>() {
@Override
public int compare(ITestResult o1, ITestResult o2) {
return o1.getStartMillis()<o2.getStartMillis()?-1:1;
}
});
treeSet.addAll(tests.getAllResults());
for (ITestResult result : treeSet) {
String name="";
//如果有参数,则使用参数的toString组合代替报告中的name
/*Object[] parameters = result.getParameters();
for(Object param:parameters){
name+=param.toString();
}*/
if(name.length()>0){
if(name.length()>50){
name= name.substring(0,49)+"...";
}
}else{
name = result.getMethod().getMethodName()/*+"---"+result.getMethod().getDescription()*/;
}
if(extenttest==null){
test = extent.createTest(name);
}else{
//作为子节点进行创建时,设置同父节点的标签一致,便于报告检索。
test = extenttest.createNode(name).assignCategory(categories);
}
//test.getModel().setDescription(description.toString());
//test = extent.createTest(result.getMethod().getMethodName());
for (String group : result.getMethod().getGroups())
test.assignCategory(group);
List<String> outputList = Reporter.getOutput(result);
for(String output:outputList){
//将用例的log输出报告中
test.debug(output);
}
if (result.getThrowable() != null) {
test.log(status, result.getThrowable());
}
else {
test.log(status, "Test " + status.toString().toLowerCase() + "ed");
}
test.getModel().setStartTime(getTime(result.getStartMillis()));
test.getModel().setEndTime(getTime(result.getEndMillis()));
}
}
}
private Date getTime(long millis) {
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(millis);
return calendar.getTime();
}
}
4.2.2xml中添加监听路径:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="Kinfe is apiAutoTest" preserve-order="true" allow-return-values="true">
<listeners>
<listener class-name="com.autotest.config.ExtentTestNGIReporterListener"/>
</listeners>
</suite>
4.2.3pom中需要添加的依赖:
<dependency>
<groupId>com.relevantcodes</groupId>
<artifactId>extentreports</artifactId>
<version>2.41.1</version>
</dependency>
<dependency>
<groupId>com.vimalselvam</groupId>
<artifactId>testng-extentsreport</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>com.aventstack</groupId>
<artifactId>extentreports</artifactId>
<version>3.0.6</version>
</dependency>
</dependency>
4.2.4最后生成的报告html文件(超级好看的报告插件完美兼容TestNG:
注:因为我写了用例所以跑的报告内容会与上面不一致,按照步骤添加,你也能生成帅帅的报告!