一、前言
前面幾篇講了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框架設計初衷呢