前面说过了,如果你稍微懂得TestNG这个单元测试框架,到目前这个简单的Java接口自动化测试框架主体的骨架部分已经完成设计并实现。这篇,继前篇的基础上,把测试用例中获取响应状态码和响应数据转换成JSON格式这些经常重复的代码,给提取出来,构造成方法来调用。然后就是给这个框架添加一个日志输出功能,方便得到运行结果和运行出错的情况下的debug。
1.添加log输出支持
1.1 maven依赖引入
这里,我试过apache log4j.jar 和slf4j.jar,由于log4j在maven项目上不能自动识别log4j.properties这个资源文件,最后我还是选择了maven引入slf4j.jar,在maven 配置文件pom.xml添加如下依赖,然后保存。
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.2</version> </dependency>
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<String,String> headermap) throws ClientProtocolException, IOException { //创建一个可关闭的HttpClient对象 CloseableHttpClient httpclient = HttpClients.createDefault(); //创建一个HttpGet的请求对象 HttpGet httpget = new HttpGet(url); //加载请求头到httpget对象 for(Map.Entry<String, String> 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<String,String> 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<String, String> 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<String,String> headerMap) throws ClientProtocolException, IOException { CloseableHttpClient httpclient = HttpClients.createDefault(); HttpPut httpput = new HttpPut(url); httpput.setEntity(new StringEntity(entityString)); for(Map.Entry<String, String> 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持续集成和测试。
由于个人在大型项目接口自动化方面经验欠缺,只能完成目前这个简单的接口自动化测试框架。所以,这个接口自动化测试框架设计系列文章就先到这里结束。最后,贴出整个项目的完整源码,百度云链接,点击这里。