Httpclient 介面自動化

domains90發表於2020-05-25

好久木寫啦!!!好久木寫啦!!!
心血來潮分享點小白的東西!!!
廢話少說直接乾貨!!!
本文核心是將如何從資料驅動開始,以報告結尾的形式來實現 “很多剛入行朋友們” 所需要的介面自動化
型別:驅動:excel
核心 jar:httpclient

編譯:maven(跟本文所講有點沾不著邊)
自動化框架 :testng(這個支援併發所以實現了併發的方法)
報告:ztest(有在之前 tester 老大哥的開源報告上實現了修改)
總結關鍵字:httpclient+maven+testng+ztest+excel

資料驅動實現主要是透過 jxl 包實現
請點這裡:https://testerhome.com/topics/15681

maven pom 配置:

<!-- httpclient依賴 -->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.5</version>
</dependency>

<!-- json解析依賴 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.47</version>
</dependency>

<!-- testng依賴 -->
<dependency>
    <groupId>org.testng</groupId>
    <artifactId>testng</artifactId>
    <version>6.14.3</version>
    <scope>test</scope>
</dependency>

其實實現可以很簡單,因為在實現的過程中加入了併發的支援所以實現起來就變成了下面這樣子(寫的很水不喜勿噴)
請求類具體實現方式:

httpclient 配置類:這個配置類主要目的分為三類
①如何支援 https 請求
②如何支援執行緒池
③自定義請求重試的機制

https 是安全的 ssl 請求,官方給出的要想支援 https 請求就必須繞過請求證書,至於如何繞過需要實現一個 X509TrustManager 的介面

private static SSLContext createIgnoreVerifySSL() {
        SSLContext sc = null;
        try {
            sc = SSLContext.getInstance("SSLv3");
        } catch (NoSuchAlgorithmException e) {
            logger.error("演算法異常", e);
        }
        // 實現一個X509TrustManager介面,用於繞過驗證,不用修改裡面的方法
        X509TrustManager trustManager = new X509TrustManager() {
            public void checkClientTrusted(java.security.cert.X509Certificate[] paramArrayOfX509Certificate,
                    String paramString) throws CertificateException {
            }

            public void checkServerTrusted(java.security.cert.X509Certificate[] paramArrayOfX509Certificate,
                    String paramString) throws CertificateException {
            }

            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        };

        try {
            sc.init(null, new TrustManager[] { trustManager }, null);
        } catch (KeyManagementException e) {
            logger.error("金鑰管理異常", e);
        }
        return sc;
    }

這樣子下來 ssl 的配置就有了,然後在 httpclient 裡面實現執行緒池的方法裡面配置這個就好了
這樣子就得到了執行緒池的物件,然後再給執行緒池一頓配置,具體程式碼有註釋解釋
第三個請求自定義重試機制(也可使用預設的重試機制,不實現這個方法,去掉這個配置就好了 setRetryHandler(myRetryHandler)),我偏不信邪我就喜歡重寫一個
這樣子下來我需要的一些配置就有了

public synchronized static CloseableHttpClient createClient() {

    SSLContext sslcontext = createIgnoreVerifySSL();

    // 驗證http,https請求,使用預設的連線請求,如有自定義需要
    // 請檢視https://hc.apache.org/httpcomponents-client-4.5.x/tutorial/html/connmgmt.html#d5e449,官方自定義證書驗證文件
    Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory> create()
            .register("http", PlainConnectionSocketFactory.INSTANCE)
            .register("https", new SSLConnectionSocketFactory(sslcontext)).build();
    // 執行緒池設定
    PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry);// registry
    // 最大連線總數
    cm.setMaxTotal(MAX_CONN);
    // 預設路由最大連線數
    cm.setDefaultMaxPerRoute(Max_PRE_ROUTE);
    // 主機埠最大連線數
    HttpHost localhost = new HttpHost("music.migu.cn", 8000);
    cm.setMaxPerRoute(new HttpRoute(localhost), MAX_ROUTE);
    // http請求重試處理機制,重寫方法
    HttpRequestRetryHandler myRetryHandler = new HttpRequestRetryHandler() {
        public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
            if (executionCount >= 10) {
                logger.error("重試次數超過5", new Throwable("重試次數超過5"));
                return false;
            }
            if (exception instanceof InterruptedIOException) {
                logger.error("中斷IO異常", new Throwable("中斷IO異常"));
                return false;
            }
            if (exception instanceof UnknownHostException) {
                new Throwable().getMessage();
                logger.error("未知主機");
                return false;
            }
            if (exception instanceof ConnectTimeoutException) {
                logger.error("連線超時", new Throwable("連線超時"));
                return false;
            }
            if (exception instanceof SSLException) {
                logger.error("sll握手異常", new Throwable("sll握手異常"));
                return false;
            }
            HttpClientContext clientContext = HttpClientContext.adapt(context);
            HttpRequest request = clientContext.getRequest();
            boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
            if (idempotent) {
                logger.error("帶有請求的實體自動重試", new Throwable());
                return true;
            }
            return false;
        }
    };
    // 建立自定義將請求重試機制新增進去;客戶端分createDefault() 與 custom()
    CloseableHttpClient httpclient = HttpClients.custom().setKeepAliveStrategy(getkeepAliveStrat())
            .setConnectionManager(cm).setRetryHandler(myRetryHandler).build();
    return httpclient;
}

配置有了之後怎麼辦呢,那當然是實現請求啊
這裡需要給大家科普一下頭,頭分為請求頭跟響應頭(網上大佬們寫的我覺得還不錯),因為請求頭能自定義還是說一下比較好
請求方的 http 報頭結構:通用報頭 | 請求報頭 | 實體報頭
響應方的 http 報頭結構:通用報頭 | 響應報頭 | 實體報頭

Accept 代表傳送端(客戶端)希望接受的資料型別。
比如:Accept:text/xml; /是所有型別都接受
代表客戶端希望接受的資料型別是 xml 型別

Content-Type 代表傳送端(客戶端 | 伺服器)傳送的實體資料的資料型別。
比如:Content-Type:text/html;
代表傳送端傳送的資料格式是 html。

解釋完了然後開始實現 get,post 請求;實現請求之前需要在寫一個 setCookie 的方法
寫完了 cookie 之後就就可以當個人了

public BasicHeader setCookie(String head) {
    BasicHeader cookie = new BasicHeader("Cookie", head);
    return cookie;
}

接下來是 get 方法
get 方法裡面有兩點必須要說明的 一個是 response 響應頭資訊,一個是 response 響應實體
response 響應頭:getReponsemes(response) 這個類實現瞭解析響應頭裡面的各種資訊裡面包含了兩種方法

response 響應實體:getResponseEntity(getEntity) 響應實體 httpclien 官網給了一個流解析的 api,但是這個 api 在某些時候會有 bug 所以這個流的解析基礎上面又加入了一個解析判斷,都有註釋有註釋你們看的懂的

這兩個方法我就不細說了,後面我會放上 git 連結看官們可以自取

public List<String> get(String url, String head) throws Exception {
    List<String> resList = new ArrayList<String>();
    CloseableHttpClient httpclient = HttpClientMain.createClient();// 建立客戶端
    CloseableHttpResponse response = null;// 建立響應
    String res = null;// 請求結果
    String statu = null;
    int status;// 狀態碼
    HttpEntity getEntity = null;
    HttpGet get = null;
    HttpClientContext context = HttpClientContext.create();
    BasicHeader cookie = setCookie(head);
    get = new HttpGet(url);
    // get.setRequestProperty("Content-Type",
    // "application/x-www-form-urlencoded;charset=utf-8");
    // get.setRequestProperty("accept", "*/*");
    // get.setHeader("HTTP_CLIENT_IP", "192.168.10.100");
    get.addHeader("user-agent",
            "Mozilla/5.0 (Windows NT 6.1; Win64; x64)AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36");
    get.addHeader("Content-Type", CONTENT_TYPE_FORM_URLENCODED);
    get.addHeader("accept", "*/*");
    get.setHeader(cookie);
    if (httpclient != null) {
        response = httpclient.execute(get, context);
        if (response != null) {
            status = response.getStatusLine().getStatusCode();
            getEntity = response.getEntity();
            statu = response.getStatusLine().toString();
            res = getResponseEntity(getEntity);
            resList.add(getReponsemes(response).toString());
            resList.add(statu);
            resList.add(res);
            if (status != 200) {
                throw new Exception(res);
            }
        }
    }
    closeAll(httpclient, response);
    return resList;
}

post 方法:post 方法多了一個請求引數至於怎麼

public List<String> post(String url, Map<String, Object> params, String head) throws Exception {
    List<String> resList = new ArrayList<String>();
    CloseableHttpClient httpclient = null;// 建立客戶端
    CloseableHttpResponse response = null;// 建立響應
    HttpEntity postEntity = null;// 請求結果
    HttpPost httpPost = null;// 請求方法
    int statusCode;// 狀態碼
    String res = null;
    String statu = null;
    BasicHeader cookie = setCookie(head);
    httpclient = createClient();
    HttpClientContext context = HttpClientContext.create();
    httpPost = new HttpPost(url);
    httpPost = setPostParams(httpPost, params, context, httpclient);
    httpPost.addHeader("user-agent",
            "Mozilla/5.0 (Windows NT 6.1; Win64; x64)AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36");
    httpPost.addHeader("Content-Type", CONTENT_TYPE_FORM_URLENCODED);
    httpPost.addHeader("accept", "*/*");
    httpPost.setHeader(cookie);
    response = httpclient.execute(httpPost, HttpClientContext.create());
    statusCode = response.getStatusLine().getStatusCode();
    // getReponsemes(response);

    if (response != null) {
        statusCode = response.getStatusLine().getStatusCode();
        postEntity = response.getEntity();
        statu = response.getStatusLine().toString();
        res = getResponseEntity(postEntity);
        resList.add(getReponsemes(response).toString());
        resList.add(statu);
        resList.add(res);
        if (statusCode != 200) {
            throw new Exception(res);
        }
    }
    return resList;
}

好了到這裡基本的主要方法都有了,然後利用 testng 去實現這些請求了
如果你深入瞭解 testng 這些看起來就很簡單了
一個資料驅動方法,一個 test 方法,一個 ztest 的監聽就完事了

@Listeners({ ZTestReport.class })
public class TestTemplate extends TestBase {
    private String excelPath = prop1.getProperty("ExcelPath");
    private String sheetName = "api";
    private List<String> re = null;
    @Test(dataProvider = "data", threadPoolSize = 1, timeOut = 0, invocationCount = 1)
    public synchronized void getData(String num, String nicName, String httpType, String reMethond, String host,
            String path, String head, String paramType, String param, String verisy) throws Exception {

        // 獲取當前執行緒id System.out.println(Thread.currentThread().getId());
        SendRequest sr = new SendRequest(num, nicName, httpType, reMethond, host, path, head, paramType, param, verisy);
        re = sr.request();
        for(String entry:re){
        Reporter.log(entry);
    }

}

    /**
     * 資料驅動
     */
    @DataProvider(name = "data", parallel = true)
    public Object[][] dataProvider() throws IOException, Exception {
        return ExcelDataParam.readExcelDataParam(excelPath, sheetName);
    }

然後只用到了 test 標籤至於為什麼完全是為了併發,至於是不是併發效果各位看官們可以列印時間到毫秒來看看,也可以列印執行緒 id,至於支援多少併發完全取決於你的機器支援多少執行緒這裡不多講,幹活基本就這麼多了

少了一個 SendRequest
這個類主要是繼承了配置檔案(比如你想配置開發,測試,生產的主機可以解除安裝配置檔案裡面)
這個類主要就是根據驅動裡面的引數來判斷怎麼去執行,各個引數為空該怎麼執行,引數型別不同該怎麼執行沒有技術含量的

public class SendRequest extends TestBase {
    private String num;// 編號
    private String nicName;// 用例名稱
    private String httpType;// 請求協議型別
    private String reMethond;// 請求方法
    private String host;// 主機
    private String path;// 路徑
    private String head;// 請求頭
    private String paramType;// 引數型別 json param
    private String param;// 請求引數
    private String verisy;// 驗證點

    public SendRequest(String num, String nicName, String httpType, String reMethond, String host, String path,
            String head, String paramType, String param, String verisy) {
        this.num = num;
        this.nicName = nicName;
        this.httpType = httpType;
        this.reMethond = reMethond;
        this.host = host;
        this.path = path;
        this.head = head;
        this.paramType = paramType;
        this.param = param;
        this.verisy = verisy;
    }

    /**
     * 根據驅動型別來選擇呼叫方法與引數型別介面呼叫類
     */
    public List<String> request() throws Exception {
        List<String> re = null;
        if (path == null) {
            path = "/";
        }
        if (host == null) {
            host = prop1.getProperty("Host");
        }

        HttpClientMain req = new HttpClientMain();
        if (reMethond.equalsIgnoreCase("get")) {
            re = req.get(httpType + "://" + host + path, head);
        } else {
            // 如果是json引數先把字串轉json,在把json轉map
            if (paramType.equalsIgnoreCase("json")) {
                JSONObject json = JSONObject.parseObject(param);
                re = req.post(httpType + "://" + host + path, JsonToMap.jsonToMap(json), head);
            } else {
                re = req.post(httpType + "://" + host + path, ParToMap.strToMap(param), head);

            }
        }
        return re;
    }

}

至於報告是怎麼實現的:https://testerhome.com/topics/10802 請看一下這位大佬的,對了大佬的報告沒有驗證點,所以我加上了後面 git 上面的 rereport 就是加了驗證點的報告,大佬好一手 jqury 害的我百度半天才加上

基本就這些了放上 git:https://gitee.com/domains90/HttpClient.git

有疑問的跟我一樣的小白們留言就好看到就會回
如果想罵人的請看這裡:我手癢想些,就是手癢啊啊啊啊啊啊

相關文章