一、前言
前面几篇讲了testng和httpclient的基本使用,掌握这些知识后足够可以开展新项目了,因为只有在项目中才会遇到各种新问题,才会推动自己去学习更多的东西。本篇主要会以贴代码的形式去讲述自己做的项目,不会有太多的文字描述了。
以前用httprunner做过一个项目,本篇的项目 用例设计原理同httprunner(感兴趣的可以去翻翻我之前的博文),大概就是把公共的东西抽出来(比如登录 、环境),然后一个菜单一个脚本文件,文件里可以包含多个用例。
本篇涉及到的知识:
- HttpClient的post、get、put请求封装
- java的实例、成员变量、局部变量
- jsonpath提取响应字段(用的是阿里的fastjson)
- 正则表达式提取响应字段
- testng运行
项目用例的主线流程:登录 > 新建订单 > 录入资料后发货
二、项目结构展示
三、项目详细示例
1、common包下面的HttpClientUtils.java
说明:封装了httpclient请求,以及业务系统的环境地址
用处:业务系统可调用封装好的代码直接发送httpclient请求,减少代码冗余
package com.tech.common; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.StatusLine; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.CloseableHttpResponse; 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 java.io.IOException; /** * @author 一加一 * @date 2021-11-18 * HttpClient 工具类 */ public class HttpClientUtils { /** * 定义host,环境地址 * @return */ public static String host(){ String host = "https://xxx-api.xxx.cn"; return host; } /** * get请求 * @param url * @param token * @return */ public static String doGet(String url,String token) throws IOException{ try { //创建浏览器对象 HttpClient httpClient = HttpClients.createDefault(); //创建get请求对象 HttpGet httpGet = new HttpGet(url); //添加get请求头 httpGet.setHeader("Accept","application/json"); httpGet.setHeader("Content-Type","application/json"); httpGet.setHeader("Token",token); //执行get请求 HttpResponse response = httpClient.execute(httpGet); //请求发送成功,并得到响应 if(response.getStatusLine().getStatusCode() == HttpStatus.SC_OK){ //读取服务器返回的json字符串 String jsonString = EntityUtils.toString(response.getEntity()); //将json字符串return return jsonString; } } catch (IOException e){ e.printStackTrace(); } return null; } /** * post请求(用于请求json格式的参数) * @param url * @param params * @param token * @return */ public static String doPost(String url,String params,String token) throws IOException{ //创建浏览器对象 CloseableHttpClient httpClient = HttpClients.createDefault(); //创建httpPost对象 HttpPost httpPost = new HttpPost(url); //添加httpPost的请求头 httpPost.setHeader("Accept","application/json"); httpPost.setHeader("Content-Type","application/json"); httpPost.setHeader("Token",token); //将请求参数加入到Entity StringEntity entity = new StringEntity(params,"UTF-8"); httpPost.setEntity(entity); //定义响应变量,初始值为null CloseableHttpResponse response = null; try { //执行请求并用变量接收返回的响应值 response = httpClient.execute(httpPost); //获取响应的状态 StatusLine status = response.getStatusLine(); //获取响应的code int state = status.getStatusCode(); //SC_OK=200 if(state == HttpStatus.SC_OK){ HttpEntity responseEntity = response.getEntity(); String jsonString = EntityUtils.toString(responseEntity); return jsonString; } else { System.out.println(("返回错误")); } } finally { if(response!=null){ try { response.close(); }catch (IOException e){ e.printStackTrace(); } } try { httpClient.close(); }catch (IOException e){ e.printStackTrace(); } } return null; } /** * put请求(用于请求json格式的参数) * @param url * @param params * @param token * @return */ public static String doPut(String url,String params,String token) throws IOException{ CloseableHttpClient httpClient = HttpClients.createDefault(); HttpPut httpPut = new HttpPut(url); httpPut.setHeader("Accept","application/json"); httpPut.setHeader("Content-Type","application/json"); httpPut.setHeader("Token",token); StringEntity entity = new StringEntity(params,"UTF-8"); httpPut.setEntity(entity); CloseableHttpResponse response = null; try { response = httpClient.execute(httpPut); StatusLine status = response.getStatusLine(); int state = status.getStatusCode(); if(state == HttpStatus.SC_OK){ HttpEntity responseEntity = response.getEntity(); String jsonString = EntityUtils.toString(responseEntity); return jsonString; } else { System.out.println(("返回错误")); } } finally { if(response!=null){ try { response.close(); }catch (IOException e){ e.printStackTrace(); } } try { httpClient.close(); }catch (IOException e){ e.printStackTrace(); } } return null; } }
2、testng包下面的Config.java
说明:登录系统A的用例
用处:【登录】,主要为了获取登录成功后动态生成的token,业务系统的接口请求时需要用到动态的token
package com.tech.testng; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import org.apache.http.HttpEntity; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; import org.testng.annotations.Test; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; public class Config { //定义类的成员变量 String code = "";//生成token接口需要用到code String Token=""; String host = "https://qa-xxx-api.xxx.cn";//登录环境域名(登录系统跟业务系统是2个不同的系统,所以这里又重新定义了个host) String loginFromServer = "https://xxx.cn";//指向对应系统环境域名 String username = "李白";//登录账号 String password = "123456";//登录密码 @Test(description ="登录接口") public void Login() throws IOException { String url = host+"/saas/auth/login"; CloseableHttpClient httpClient = HttpClients.createDefault(); HttpPost httpPost = new HttpPost(url); httpPost.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36"); httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); List<BasicNameValuePair> param = new ArrayList<>(4); param.add(new BasicNameValuePair("loginFromServer", loginFromServer)); param.add(new BasicNameValuePair("username", username)); param.add(new BasicNameValuePair("password", password)); UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(param, StandardCharsets.UTF_8); httpPost.setEntity(formEntity); CloseableHttpResponse response = httpClient.execute(httpPost);//执行请求 //System.out.println("响应状态为:" + response.getStatusLine()); String body = EntityUtils.toString(response.getEntity());//获取响应内容 System.out.println("Login的响应内容为:" + body); JSONObject jsonObject = JSON.parseObject(body);//转换成json格式 String data = jsonObject.getString("data");//获取到data值 //拼接双引号: "\""+ +"\"" Pattern pattern = Pattern.compile("code=(.*)");//正则表达式提取code Matcher matcher = pattern.matcher(data); if(matcher.find()) { code = matcher.group(1); System.out.println("提取的code为:" + code); } } @Test(description = "生成token接口",dependsOnMethods = {"Login"}) public String exchangeToken() throws IOException { String url = host+"/saas/auth/exchangeToken"; CloseableHttpClient httpClient = HttpClients.createDefault(); HttpPost httpPost = new HttpPost(url); httpPost.addHeader("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36"); httpPost.addHeader("Content-Type","application/x-www-form-urlencoded; charset=UTF-8"); List<BasicNameValuePair> param = new ArrayList<>(1); param.add(new BasicNameValuePair("code",code)); UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(param, StandardCharsets.UTF_8); httpPost.setEntity(formEntity);//执行请求 CloseableHttpResponse response = httpClient.execute(httpPost);//获取响应内容 HttpEntity res = response.getEntity(); String message = EntityUtils.toString(res); //String body = EntityUtils.toString(response.getEntity()); //System.out.println("exchangeToken的响应内容为:"+body); System.out.println("token响应"+message); JSONObject jsonObject = JSON.parseObject(message);//转换成json格式 JSONObject jsonObject1 = jsonObject.getJSONObject("data");//获取到data值 Token = jsonObject1.getString("ssoToken");//获取data对象里的ssoToken,并赋值给Token System.out.println("exchangeToken生成的Token为:"+Token); //返回Token供其他用例引用 return Token; } }
3、demand包下面的Demand.java
说明:业务系统B的用例
用处:【新建订单】
package com.tech.demand; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.tech.common.HttpClientUtils; import com.tech.testng.Config; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; import java.io.IOException; public class Demand { //定义全局变量 String token =""; String styleCode = ""; //调用类的静态成员方法 String host = HttpClientUtils.host(); @BeforeTest public void Token() throws IOException { //如何得到类的对象:类名 对象名 = new 类名() 得到Config类的对象 Config config = new Config(); config.Login(); //如何使用对象:1、访问属性:对象名.成员变量 2、访问行为:对象名.方法名(...) token = config.exchangeToken();//调用Config类的方法,用全局变量token接收方法返回的Token值 System.out.println(token); } @Test(description = "新建订单") public void putOrder() throws IOException { String url = host+"/order/web/v1/create"; //新建订单 String params = "{"Id":"68163112","Name":"短袖"}"; //直接调用封装好的请求方法 String body = HttpClientUtils.doPut(url,params,token); System.out.println("新建订单成功"+body); } @Test(description = "订单列表查询",dependsOnMethods = {"putOrder"}) public String postPage() throws IOException { String url = host+"/web/v1/order/page"; String params = "{"pageNum":1,"pageSize":20}"; String body = HttpClientUtils.doPost(url,params,token); //jsonpath提取响应字段 JSONObject jsonObject = JSON.parseObject(body); JSONObject data = jsonObject.getJSONObject("data");//得到data的json对象 JSONArray list = data.getJSONArray("list");//得到data中的list对象,并用list保存 JSONObject jsonObject1 = list.getJSONObject(0);//获取list下标为0的数据 styleCode = jsonObject1.getString("styleCode");//提取styleCode字段 System.out.println(styleCode); return styleCode; } }
4、demand包下面的Production.java
说明:业务系统B的用例
用处:【录入资料后发货】
package com.tech.demand; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.tech.common.HttpClientUtils; import com.tech.testng.Config; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; import java.io.IOException; public class Production { //定义全局变量 String token =""; String styleId =""; String demandDetailId =""; String orderId =""; //调用类的静态成员方法 String host = HttpClientUtils.host(); @BeforeTest public void Token() throws IOException { //如何得到类的对象:类名 对象名 = new 类名() 得到Config类的对象 Config config = new Config(); config.Login(); //如何使用对象:1、访问属性:对象名.成员变量 2、访问行为:对象名.方法名(...) token = config.exchangeToken();//调用Config类的方法,用全局变量ssotoken接收方法返回的Token值 System.out.println(token); } @Test(description = "发货列表查询") public void getPage() throws IOException { String url = host+"/web/v1/info/page?pageNum=1&pageSize=20"; String body = HttpClientUtils.doGet(url,token); System.out.println("发货列表查询成功,响应内容为:"+body); JSONObject jsonObject = JSON.parseObject(body); JSONObject data = jsonObject.getJSONObject("data");//得到data的json对象 JSONArray list = data.getJSONArray("list");//得到data中的list对象,并用list保存 JSONObject jsonObject1 = list.getJSONObject(0);//获取list下标为0的数据 styleId = jsonObject1.getString("styleId"); demandDetailId = jsonObject1.getString("demandDetailId"); } @Test(description = "发货-附件资料上传",dependsOnMethods = {"getPage"}) public void postAdd() throws IOException{ String url = host+"/web/v1/info/add"; //报价单【附件类型(3-报价单,4-商品单)】 String params3 = "{"attachmentType":"3","attachmentUrl":"702074.jpeg"}"; //商品单 String params4 = "{"attachmentType":"4","attachmentUrl":"902034.jpeg"}"; String body = HttpClientUtils.doPost(url,params3,token); String body1 = HttpClientUtils.doPost(url,params4,token); System.out.println("报价单上传成功"+body); System.out.println("商品单上传成功"+body1); } @Test(description = "获取发货详情",dependsOnMethods = {"postAdd"}) public void getDetail() throws IOException{ String url = host+"/web/v1/production-order/detail/detail-id?demandDetailId="+demandDetailId; String body = HttpClientUtils.doGet(url,token); JSONObject jsonObject = JSON.parseObject(body); JSONObject data = jsonObject.getJSONObject("data");//得到data的json对象 orderId = data.getString("orderId");//提取orderId } @Test(description = "发货资料提交",dependsOnMethods = {"getDetail"},enabled = true) public void postSubmit() throws IOException{ String url = host+"/web/v1/order/submit/"+styleId; String params = "{}"; String body = HttpClientUtils.doPost(url,params,token); System.out.println("发货成功,响应为:"+body); } }
5、执行TestNG
1)方式一:编程代码方式执行
在testng包下面新建test.java,代码如下:
package com.tech.testng; import com.tech.demand.*;import org.testng.TestNG; import java.io.IOException; public class test { public static void main(String[] args) throws IOException { //在main函数调用 TestNG testNG = new TestNG(); Class[] classes = {Demand.class, Production.class}; testNG.setTestClasses(classes); testNG.run(); } }
2)方式二:XML方式执行
在HelloTest-src目录下新建testng.xml,代码如下:
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd" > <suite name="Suite1" verbose="1" > <test name="test1" > <classes> <class name="com.tech.demand.Demand"></class> <class name="com.tech.demand.Production"></class> </classes> </test> </suite>
6、执行结果
忽略,这里不贴图了
四、总结
以上就是整个项目的代码了,当然业务系统的用例只贴出了一部分,但并不影响演示项目实例。
因为刚开始用testng做,先罗列下该项目存在的问题,看看后面有没有办法可以优化:
- 现状1:业务系统的.java用例文件,每次执行前都要先实例登录
- ——>存在的问题:倘若.java用例文件有20个,那执行完一个项目则需要重复登录20次
- 现状2:为了让业务系统的每个.java用例文件都可以独立运行,目前的处理方式是先列表查询出最新的数据去处理
- ——>存在的问题:倘若刚好有人同时创建了一条数据,那该用例查出来的最新数据就不是自己上一步创建的,而是别人的,至此会导致后面用例可能出现问题
- 现状3:一个.java用例文件,可能包含很多@Test,因为是主流程自动化,入参要用到前面接口的出参,所以采用了dependsOnMethods用例依赖
- ——>存在的问题:不太明白这种方式是否符合“用例要独立可运行”的原则,因为如果依赖用例失败的话,该用例就会失败
- 现状4:目前个人用的是编程代码方式去执行用例
- ——>存在的问题:看了testng官方文档,以及查了很多网上资料,都写的是用xml方式去执行,是否个人使用的不合testng框架设计初衷呢