前面说过了,如果你稍微懂得TestNG这个单元测试框架,到目前这个简单的Java接口自动化测试框架主体的骨架部分已经完成设计并实现。这篇,继前篇的基础上,把测试用例中获取响应状态码和响应数据转换成JSON格式这些经常重复的代码,给提取出来,构造成方法来调用。然后就是给这个框架添加一个日志输出功能,方便得到运行结果和运行出错的情况下的debug。
1.添加log输出支持
1.1 maven依赖引入
这里,我试过apache log4j.jar 和slf4j.jar,由于log4j在maven项目上不能自动识别log4j.properties这个资源文件,最后我还是选择了maven引入slf4j.jar,在maven 配置文件pom.xml添加如下依赖,然后保存。
org.slf4j
slf4j-log4j12
1.7.2
1.2 新建src/main/config资源包
在Eclipse上点击当前项目名,右键new -source folder,输出src/main/config,点击确定,然后在src/main/config下新建一个log4j.properties文件,内容如下。
### set log levels ###
log4j.rootLogger = INFO, stdout, file
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss SSS} %-5p %c{1}:%L - %m%n
log4j.appender.file = org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.File = ./log/apilog.log
# overwirte the old log file
log4j.appender.file.Append = false
##
log4j.appender.file.Threshold = INFO
log4j.appender.file.layout = org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss SSS} %-5p %c{1}:%L - %m%n
1.3 在项目根目录下新建log文件夹
新建这个文件夹是上面log4j.properties文件我们设置的日志保存文件路径是在./log文件夹下。大致的项目结构图如下
2.优化RestClient.java内容
主要是把在测试断言中经常需要拿到的响应状态码和json解析之前需要把响应内容转换成json格式,这部分内容给提取到方法里。然后我们写上log输出。(完整代码请看文件结尾处百度云链接)
2.1.RestClient类添加日志输出和代码提取成方法
package com.qa.restclient;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.apache.http.ParseException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
public class RestClient {
final static Logger Log = Logger.getLogger(RestClient.class);
/**
* 不带请求头的get方法封装
* @param url
* @return 返回响应对象
* @throws ClientProtocolException
* @throws IOException
*/
public CloseableHttpResponse get (String url) throws ClientProtocolException, IOException {
//创建一个可关闭的HttpClient对象
CloseableHttpClient httpclient = HttpClients.createDefault();
//创建一个HttpGet的请求对象
HttpGet httpget = new HttpGet(url);
//执行请求,相当于postman上点击发送按钮,然后赋值给HttpResponse对象接收
Log.info("开始发送get请求...");
CloseableHttpResponse httpResponse = httpclient.execute(httpget);
Log.info("发送请求成功!开始得到响应对象。");
return httpResponse;
}
/**
* 带请求头信息的get方法
* @param url
* @param headermap,键值对形式
* @return 返回响应对象
* @throws ClientProtocolException
* @throws IOException
*/
public CloseableHttpResponse get (String url,HashMap headermap) throws ClientProtocolException, IOException {
//创建一个可关闭的HttpClient对象
CloseableHttpClient httpclient = HttpClients.createDefault();
//创建一个HttpGet的请求对象
HttpGet httpget = new HttpGet(url);
//加载请求头到httpget对象
for(Map.Entry entry : headermap.entrySet()) {
httpget.addHeader(entry.getKey(), entry.getValue());
}
//执行请求,相当于postman上点击发送按钮,然后赋值给HttpResponse对象接收
CloseableHttpResponse httpResponse = httpclient.execute(httpget);
Log.info("开始发送带请求头的get请求...");
return httpResponse;
}
/**
* 封装post方法
* @param url
* @param entityString,其实就是设置请求json参数
* @param headermap,带请求头
* @return 返回响应对象
* @throws ClientProtocolException
* @throws IOException
*/
public CloseableHttpResponse post (String url, String entityString, HashMap headermap) throws ClientProtocolException, IOException {
//创建一个可关闭的HttpClient对象
CloseableHttpClient httpclient = HttpClients.createDefault();
//创建一个HttpPost的请求对象
HttpPost httppost = new HttpPost(url);
//设置payload
httppost.setEntity(new StringEntity(entityString));
//加载请求头到httppost对象
for(Map.Entry entry : headermap.entrySet()) {
httppost.addHeader(entry.getKey(), entry.getValue());
}
//发送post请求
CloseableHttpResponse httpResponse = httpclient.execute(httppost);
Log.info("开始发送post请求");
return httpResponse;
}
/**
* 封装 put请求方法,参数和post方法一样
* @param url
* @param entityString,这个主要是设置payload,一般来说就是json串
* @param headerMap,带请求的头信息,格式是键值对,所以这里使用hashmap
* @return 返回响应对象
* @throws ClientProtocolException
* @throws IOException
*/
public CloseableHttpResponse put (String url, String entityString, HashMap headerMap) throws ClientProtocolException, IOException {
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpPut httpput = new HttpPut(url);
httpput.setEntity(new StringEntity(entityString));
for(Map.Entry entry : headerMap.entrySet()) {
httpput.addHeader(entry.getKey(), entry.getValue());
}
//发送put请求
CloseableHttpResponse httpResponse = httpclient.execute(httpput);
return httpResponse;
}
/**
* 封装 delete请求方法,参数和get方法一样
* @param url, 接口url完整地址
* @return,返回一个response对象,方便进行得到状态码和json解析动作
* @throws ClientProtocolException
* @throws IOException
*/
public CloseableHttpResponse delete (String url) throws ClientProtocolException, IOException {
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpDelete httpdel = new HttpDelete(url);
//发送delete请求
CloseableHttpResponse httpResponse = httpclient.execute(httpdel);
return httpResponse;
}
/**
* 获取响应状态码,常用来和TestBase中定义的状态码常量去测试断言使用
* @param response
* @return 返回int类型状态码
*/
public int getStatusCode (CloseableHttpResponse response) {
int statusCode = response.getStatusLine().getStatusCode();
Log.info("解析,得到响应状态码:"+ statusCode);
return statusCode;
}
/**
*
* @param response, 任何请求返回返回的响应对象
* @return, 返回响应体的json格式对象,方便接下来对JSON对象内容解析
* 接下来,一般会继续调用TestUtil类下的json解析方法得到某一个json对象的值
* @throws ParseException
* @throws IOException
*/
public JSONObject getResponseJson (CloseableHttpResponse response) throws ParseException, IOException {
Log.info("得到响应对象的String格式");
String responseString = EntityUtils.toString(response.getEntity(),"UTF-8");
JSONObject responseJson = JSON.parseObject(responseString);
Log.info("返回响应内容的JSON格式");
return responseJson;
}
}
2.2 TestNG测试用例添加日志输出
package com.qa.tests;
import java.io.IOException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.log4j.Logger;
import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import com.alibaba.fastjson.JSONObject;
import com.qa.base.TestBase;
import com.qa.restclient.RestClient;
import com.qa.util.TestUtil;
public class GetApiTest extends TestBase {
TestBase testBase;
String host;
String url;
RestClient restClient;
CloseableHttpResponse closeableHttpResponse;
final static Logger Log = Logger.getLogger(GetApiTest.class);
@BeforeClass
public void setUp() {
testBase = new TestBase();
//Log.info("测试服务器地址为:"+ host.toString());
host = prop.getProperty("HOST");
//Log.info("当前测试接口的完整地址为:"+url.toString());
url = host + "/api/users?page=2";
}
@Test
public void getAPITest() throws ClientProtocolException, IOException {
Log.info("开始执行用例...");
restClient = new RestClient();
closeableHttpResponse = restClient.get(url);
//断言状态码是不是200
Log.info("测试响应状态码是否是200");
int statusCode = restClient.getStatusCode(closeableHttpResponse);
Assert.assertEquals(statusCode, RESPNSE_STATUS_CODE_200, "response status code is not 200");
JSONObject responseJson = restClient.getResponseJson(closeableHttpResponse);
//System.out.println("respon json from API-->" + responseJson);
//json内容解析
String s = TestUtil.getValueByJPath(responseJson,"data[0]/first_name");
Log.info("执行JSON解析,解析的内容是 " + s);
//System.out.println(s);
Log.info("接口内容响应断言");
Assert.assertEquals(s, "Eve","first name is not Eve");
Log.info("用例执行结束...");
}
}
这里,我强调下,我在上面BeforeClass部分无法引入Log输出,上面代码我注销了日志打印,如果不注销,这块会报空指针异常,很奇怪,只有在@BeforeClass中报错,在@Test中没有,我花了一些时间,还是搞不懂这块,所以,就放弃在beforeclass部分添加日志输出。
看看在./log/api.log的日志输出效果
2018-05-26 17:39:21 864 INFO TestBase:28 - 正在读取配置文件...
2018-05-26 17:39:21 926 INFO TestBase:28 - 正在读取配置文件...
2018-05-26 17:39:21 943 INFO GetApiTest:39 - 开始执行用例...
2018-05-26 17:39:22 463 INFO RestClient:41 - 开始发送get请求...
2018-05-26 17:39:23 206 INFO RestClient:43 - 发送请求成功!开始得到响应对象。
2018-05-26 17:39:23 206 INFO GetApiTest:44 - 测试响应状态码是否是200
2018-05-26 17:39:23 207 INFO RestClient:146 - 解析,得到响应状态码:200
2018-05-26 17:39:23 209 INFO RestClient:160 - 得到响应对象的String格式
2018-05-26 17:39:23 297 INFO RestClient:163 - 返回响应内容的JSON格式
2018-05-26 17:39:23 299 INFO GetApiTest:53 - 执行JSON解析,解析的内容是 Eve
2018-05-26 17:39:23 299 INFO GetApiTest:55 - 接口内容响应断言
2018-05-26 17:39:23 300 INFO GetApiTest:57 - 用例执行结束...
说明:
这个Java接口自动化测试框架,更适合于接口的单元测试。也就是说,写接口测试用例的人员必须会Java,必须掌握TestNG的基本使用。而且所有的接口测试用例都是以一个个不同TestNG类文件组成。所以,这个框架无法帮助黑盒测试人员完成接口自动化测试,只适合会写单元测试的自动化测试人员,依赖单元测试框架去管理和运行接口测试用例,拿到测试报告。当然,后续可以扩展支持Jenkins持续集成和测试。
由于个人在大型项目接口自动化方面经验欠缺,只能完成目前这个简单的接口自动化测试框架。所以,这个接口自动化测试框架设计系列文章就先到这里结束。最后,贴出整个项目的完整源码,百度云链接,点击这里。
2018-08-21 更新
QQ群有朋友,在这个接口自动化框架基础之上,优化了很多内容,有token传参,特别是报告这块,他还特意写了博客文章总结,非常感谢 “池同学”,大家请移步到他博客去看看改良的框架。博客文章地址:https://blog.csdn.net/qq_34693151