zoukankan      html  css  js  c++  java
  • 基于Java+HttpClient+TestNG的接口自动化测试框架(十一)------ 确定接口请求数据流程并执行测试

      前面的部分,我们主要是对各种部分的数据处理进行说明。本篇来说明一下,接口请求数据的流程及一些问题的处理。

      我们知道,在进行接口测试之前,通常会对环境进行一些配置。比如:host的设定,一些固定参数的设定等等。关于环境设定的方面,我们通常还是通过xml的方式来进行设定。请参考之前的有关参数设定的章节。基于Java+HttpClient+TestNG的接口自动化测试框架(二)------配置文件的设定及读取

      在进行环境设定之后,我们需要使用TestNG来制作一个接口请求的模板类,具体来说就是所有类型的接口都可以按照这个模板来进行请求,并判定结果是否正确。

      在通常来说,接口的工作流程,可以参考下面的形式:

    处理环境参数-------->处理请求参数-------->封装请求对象------->运行请求------->分析请求返回结果并判定------->生成log和报告------>对返回结果进行提取保存。

           根据以上的流程,我们在作成这个TestNG的类:

    package testSysApi;
    
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.JSONObject;
    
    import bean.ApiDataBean;
    import configs.apiConfigs;
    
    import org.apache.http.Header;
    import org.apache.http.HttpEntity;
    import org.apache.http.HttpResponse;
    import org.apache.http.NameValuePair;
    import org.apache.http.ParseException;
    import org.apache.http.client.HttpClient;
    import org.apache.http.client.config.RequestConfig;
    import org.apache.http.client.entity.UrlEncodedFormEntity;
    import org.apache.http.client.methods.*;
    import org.apache.http.entity.ContentType;
    import org.apache.http.entity.StringEntity;
    import org.apache.http.entity.mime.HttpMultipartMode;
    import org.apache.http.entity.mime.MultipartEntityBuilder;
    import org.apache.http.entity.mime.content.FileBody;
    import org.apache.http.entity.mime.content.StringBody;
    import org.apache.http.impl.client.HttpClients;
    import org.apache.http.message.BasicHeader;
    import org.apache.http.message.BasicNameValuePair;
    import org.apache.http.util.EntityUtils;
    import org.dom4j.DocumentException;
    import org.testng.Assert;
    import org.testng.ITestContext;
    import org.testng.annotations.*;
    import org.testng.annotations.Optional;
    
    import java.io.File;
    import java.io.IOException;
    import java.io.InputStream;
    import java.nio.charset.Charset;
    import java.nio.file.Paths;
    import java.time.Duration;
    import java.time.LocalDateTime;
    import java.util.*;
    import java.util.regex.Matcher;
    
    import listener.AutoTestListener;
    import listener.RetryListener;
    import testCase.TestBase;
    import utils.fileUtil;
    import utils.randomUtil;
    import utils.reportUtil;
    
    @Listeners({ AutoTestListener.class, RetryListener.class })
    public class customTest extends TestBase {
    
        /**
         * api请求跟路径
         */
        private static String rootUrl;
    
        /**
         * 跟路径是否以‘/’结尾
         */
        private static boolean rooUrlEndWithSlash = false;
    
        /**
         * 所有公共header,会在发送请求的时候添加到http header上
         */
        private static Header[] publicHeaders;
        /**
         * 是否使用form-data传参 会在post与put方法封装请求参数用到
         */
        private static boolean requestByFormData = false;
    
        /**
         * 配置
         */
        private static apiConfigs apiConfig;
    
        /**
         * 所有api测试用例数据
         */
        protected List<ApiDataBean> dataList = new ArrayList<ApiDataBean>();
        private static HttpEntity httpEntity;
        private static HttpClient client;
    
        /**
         * 初始化测试数据
         *
         * @throws Exception
         */
        @Parameters("envName")
        @BeforeSuite
        public void init(@Optional("config.xml") String envName) throws Exception {
            String configFilePath = Paths.get(System.getProperty("user.dir")+"\config\", envName).toString();
            reportUtil.log("api config path:" + configFilePath);
            apiConfig = new apiConfigs(configFilePath);
            // 获取基础数据
            rootUrl = apiConfig.getRootUrl();
            rooUrlEndWithSlash = rootUrl.endsWith("/");
    
            // 读取 param,并将值保存到公共数据map
            Map<String, String> params = apiConfig.getParams();
            setSaveDatas(params);
            
            //读取配置xml文件,将公共请求头进行设置
            List<Header> headers = new ArrayList<Header>();
            apiConfig.getHeaders().forEach((key, value) -> {
                Header header = new BasicHeader(key, value);
                if(!requestByFormData && key.equalsIgnoreCase("content-type") && value.toLowerCase().contains("form-data")){
                    requestByFormData=true;
                }
                headers.add(header);
            });
            publicHeaders = headers.toArray(new Header[headers.size()]);
            //对HttpClient设置超时时间
            RequestConfig reqCon = RequestConfig.custom()
                    .setConnectTimeout(60000)
                    .setConnectionRequestTimeout(60000)
                    .setSocketTimeout(60000).build();
            client = HttpClients.custom().setDefaultRequestConfig(reqCon).build();
        }
    
        @Parameters({ "excelPath"})
        @BeforeTest
        public void readData(@Optional("case/test-data.xls") String excelPath,ITestContext context) throws DocumentException {
            //获取xml中所有的参数
            Map<String,String> xmlParam = context.getCurrentXmlTest().getAllParameters();
            List<String> sheetsName = new ArrayList<String>();
            /*
             * 可以指定多个sheetName的名字来进行测试
             * 形式如        <parameter name="sheetName1" value="User"></parameter>
                                <parameter name="sheetName2" value="Product"></parameter>
             *  这里如果不进行过滤,可以修改为默认进行所有sheet的测试
             */
            for(String s : xmlParam.keySet()) {
                if(s.contains("sheetName")) {
                    sheetsName.add(xmlParam.get(s));
                }
            }
            String[] sheets = sheetsName.toArray(new String[sheetsName.size()]);
            dataList = readExcelData(ApiDataBean.class, excelPath.split(";"),sheets);
        }
    
        /**
         * 过滤数据,run标记为Y的执行。
         *
         * @return
         * @throws DocumentException
         */
        @DataProvider(name = "apiDatas")
        public Iterator<Object[]> getApiData(ITestContext context)
                throws DocumentException {
            List<Object[]> dataProvider = new ArrayList<Object[]>();
            for (ApiDataBean data : dataList) {
                if (data.isRun()) {
                    dataProvider.add(new Object[] { data });
                }
            }
            return dataProvider.iterator();
        }
    
        @Test(dataProvider = "apiDatas")
        public void apiTest(ApiDataBean apiDataBean) throws Exception {
            reportUtil.log("------------------test start --------------------");
            if (apiDataBean.getSleep() > 0) {
                // sleep休眠时间大于0的情况下进行暂停休眠
                reportUtil.log(String.format("sleep %s seconds",
                        apiDataBean.getSleep()));
                Thread.sleep(apiDataBean.getSleep() * 1000);
            }
            String apiParam = buildRequestParam(apiDataBean);
            //由于headers要入参,因此Excel的
            String headers = buildRequestHeader(apiDataBean);
            // 封装请求方法
            HttpUriRequest method = parseHttpRequest(apiDataBean.getUrl(),
                    apiDataBean.getMethod(),headers,apiParam);
            String responseData;
            try {
                //增加运行时间计算显示
                LocalDateTime beginTime = LocalDateTime.now();
                // 执行
                HttpResponse response = client.execute(method);
                Long timeConsuming = Duration.between(beginTime,LocalDateTime.now()).toMillis();
                reportUtil.log("测试执行时间为:" + timeConsuming + "ms!"); 
                int responseStatus = response.getStatusLine().getStatusCode();
                reportUtil.log("返回状态码:"+responseStatus);
                if (apiDataBean.getStatus()!= 0) {
                    Assert.assertEquals(responseStatus, apiDataBean.getStatus(),
                            "返回状态码与预期不符合!");
                } 
                else {
                    // 非2开头状态码为认为是异常请求,抛出异常
                    if (200 > responseStatus || responseStatus >= 300) {
                        reportUtil.log("返回状态码非200开头:"+EntityUtils.toString(response.getEntity(), "UTF-8"));
                        throw new Exception("返回状态码异常:"+ responseStatus);
                    }
                }
                HttpEntity respEntity = response.getEntity();
                Header respContentType = response.getFirstHeader("Content-Type");
                if (respContentType != null && respContentType.getValue() != null 
                        &&  (respContentType.getValue().contains("download") || respContentType.getValue().contains("octet-stream"))) {
                    String conDisposition = response.getFirstHeader(
                            "Content-disposition").getValue();
                    String fileType = conDisposition.substring(
                            conDisposition.lastIndexOf("."),
                            conDisposition.length());
                    String filePath = "download/" + randomUtil.getRandom(8, false)
                            + fileType;
                    InputStream is = response.getEntity().getContent();
                    Assert.assertTrue(fileUtil.writeFile(is, filePath), "下载文件失败。");
                    // 将下载文件的路径放到{"filePath":"xxxxx"}进行返回
                    responseData = "{"filePath":"" + filePath + ""}";
                } else {
                    responseData=EntityUtils.toString(respEntity, "UTF-8");
                }
            } catch (Exception e) {
                throw e;
            } finally {
                method.abort();
            }
            // 输出返回数据log
            reportUtil.log("resp:" + responseData);
            // 验证预期信息
            verifyResult(responseData, apiDataBean.getVerify(),
                    apiDataBean.isContains());
    
            // 对返回结果进行提取保存。
            saveResult(responseData, apiDataBean.getSave());
        }
    
        private String buildRequestParam(ApiDataBean apiDataBean) {
            // 分析处理预参数 (函数生成的参数)
            String preParam = buildParam(apiDataBean.getPreParam());
            savePreParam(preParam);// 保存预存参数 用于后面接口参数中使用和接口返回验证中
            // 处理参数
            String apiParam = buildParam(apiDataBean.getParam());
    //        System.out.println(apiParam);
            return apiParam;
        }
        /*
         * 获取Excel文件中设置的关键性Header信息,例如:Content-Type,Authorization等
         */
        private String buildRequestHeader(ApiDataBean apiDataBean) {
            String header = "";
            header = buildParam(apiDataBean.getHeader());
            return header;
        }
        /*
         * 这里的header是Excel中设置的json字符串
         */
        private HttpUriRequest parseHttpRequest(String url, String method,String header,String param) throws ParseException, IOException {
            Map<String,String> publicMaps = new HashMap<String,String>();
            for(Header he : publicHeaders) {
                publicMaps.put(he.getName(), he.getValue());
            }
            // 处理url
            url = parseUrl(url);
            reportUtil.log("method:" + method);
            reportUtil.log("url:" + url);
            reportUtil.log("publicHeaders:" + JSONObject.toJSONString(publicMaps));
            reportUtil.log("header:" + header);
            reportUtil.log("param:" + param.replace("
    ", "").replace("
    ", ""));
            if(header != null) {
                @SuppressWarnings("unchecked")
                Map<String,String> headers = JSON.parseObject(header,HashMap.class);
                publicMaps.putAll(headers);
            }
            //使用Content-Type的值来判定具体body上传模式
            List<String> values = new ArrayList<String>();
            for(String s : publicMaps.keySet()) {
                values.add(publicMaps.get(s));
            }
            System.out.println(values);
            //upload表示上传,也是使用post进行请求
            if ("post".equalsIgnoreCase(method) || "upload".equalsIgnoreCase(method)) {
                // 封装post方法
                HttpPost postMethod = new HttpPost(url);
                Set<Map.Entry<String, String>> set = publicMaps.entrySet();
                Iterator<Map.Entry<String, String>> it = set.iterator();
                while(it.hasNext()) {
                    Map.Entry<String, String> entry = it.next();
                    //如果遇到"Content-Type:multipart/form-data"的情况,请不要加入该请求头。
                    //通过抓包可以发现,
                    //一般Content-Type:multipart/form-data 后面会加上一串 boundary=--------------------------016172816456888939258535的信息
                    //这个信息是动态变化的。
                    if(entry.getValue().equals("multipart/form-data")) {
                        continue;
                    }else {
                        postMethod.addHeader(entry.getKey(),entry.getValue());
                    }
                }    
                //根据请求头的content-type的值,来分别选择上传形式
                HttpEntity entity  = parseEntity(param,values);
                postMethod.setEntity(entity);
                return postMethod;
            } else if ("put".equalsIgnoreCase(method)) {
                // 封装put方法
                HttpPut putMethod = new HttpPut(url);
                Set<Map.Entry<String, String>> set = publicMaps.entrySet();
                Iterator<Map.Entry<String, String>> it = set.iterator();
                while(it.hasNext()) {
                    Map.Entry<String, String> entry = it.next();
                    putMethod.addHeader(entry.getKey(),entry.getValue());
                }
                HttpEntity entity  = parseEntity(param,values);
                putMethod.setEntity(entity);
                return putMethod;
            } else if ("delete".equalsIgnoreCase(method)) {
                // 封装delete方法
                HttpDelete deleteMethod = new HttpDelete(url);
                deleteMethod.setHeaders(publicHeaders);
                return deleteMethod;
            }else {
                // 封装get方法
                HttpGet getMethod = new HttpGet(url);
                Set<Map.Entry<String, String>> set = publicMaps.entrySet();
                Iterator<Map.Entry<String, String>> it = set.iterator();
                while(it.hasNext()) {
                    Map.Entry<String, String> entry = it.next();
                    getMethod.addHeader(entry.getKey(),entry.getValue());
                }
                return getMethod;    
            }
        }
    
        /**
         * 格式化url,替换路径参数等。
         *
         * @param shortUrl
         * @return
         */
        private String parseUrl(String shortUrl) {
            // 替换url中的参数
            shortUrl = getCommonParam(shortUrl);
            if (shortUrl.startsWith("http")) {
                return shortUrl;
            }
            if (rooUrlEndWithSlash == shortUrl.startsWith("/")) {
                if (rooUrlEndWithSlash) {
                    shortUrl = shortUrl.replaceFirst("/", "");
                } else {
                    shortUrl = "/" + shortUrl;
                }
            }
            return rootUrl + shortUrl;
        }
    
        /**
         * 格式化参数,根据请求头的形式来决定如何封装entity,这里主要列了三种形式。
         * @param param 参数
         * @param headerValueList 请求头列表里的数据。根据请求头的数据来决定封装形式。
         * @return
         * @throws IOException 
         * @throws ParseException 
         */
        @SuppressWarnings("unchecked")
        private HttpEntity parseEntity(String param,List<String> headerValueList) throws ParseException, IOException{
            int requestBodyNum = 0;
            for(String headerValue : headerValueList) {
                if(headerValue.contains("multipart/form-data")) {
                    requestBodyNum = 1;
                }else if(headerValue.contains("application/x-www-form-urlencoded")) {
                    requestBodyNum = 2;
                }else if(headerValue.equalsIgnoreCase("application/json")) {
                    requestBodyNum = 3;
                }
            }
            switch (requestBodyNum) {
            case 1:
                Map<String, String> paramMap = JSON.parseObject(param,HashMap.class);
                Charset charset = Charset.defaultCharset();
                MultipartEntityBuilder builder = MultipartEntityBuilder.create().setMode(HttpMultipartMode.BROWSER_COMPATIBLE)
                        .setCharset(charset);
                for (String key : paramMap.keySet()) {
                    String value = paramMap.get(key);
                    Matcher m = funPattern.matcher(value);
                    if (m.matches() && m.group(1).equals("bodyfile")) {
                        value = m.group(2);
                        builder.addPart(key, new FileBody(new File(value)));
                    } else {
                        StringBody stringBody = new StringBody(null == value ? "" : value.toString()
                                , ContentType.create(ContentType.TEXT_PLAIN.getMimeType(), charset)); //编码
                        builder.addPart(key, stringBody);
                    }
                }
                httpEntity = builder.build();
                break;
            case 2:
                Map<String, String> bodyMaps = JSON.parseObject(param,HashMap.class);
                List<NameValuePair> bodyParams = new ArrayList<NameValuePair>();
                for(String key: bodyMaps.keySet()) {
                    String value = bodyMaps.get(key);
                    bodyParams.add(new BasicNameValuePair(key,value));
                }
                httpEntity = new UrlEncodedFormEntity(bodyParams,"UTF-8");
                break;
            case 3 :
                httpEntity = new StringEntity(param,"UTF-8");
                break;
            }
            return httpEntity;
        }
    }

      从上面的模板代码,我们完成了整个接口请求的流程。在实际的操作中,我们只需要指定case文件(Excel)和配置文件(xml),就可以对接口进行自动化测试了。当然,运行TestNG,我们也是采用xml的运行方式,这个的写法就很简单了。指明需要运行的类和方法,并配置好监听器用来生成报告即可,下面给出一个模板。

    <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
    <suite name="SYS接口自动化测试" verbose="1" preserve-order="true" parallel="false">
        <test name="SYS自动化测试用例">
            <parameter name="excelPath" value="case/test-data.xls"></parameter>
            <parameter name="sheetName1" value="User"></parameter>
            <parameter name="sheetName2" value="Product"></parameter>
            <classes>
                <class name="testSysApi.customTest">
                    <methods>                            
                        <include name="apiTest"></include>
                    </methods>
                </class>    
            </classes>
        </test>
        <listeners>    
            <listener class-name="listener.AutoTestListener"></listener>
            <listener class-name="listener.RetryListener"></listener>
            <!-- ExtentReport 报告  -->
            <listener class-name="listener.ExtentTestNGIReporterListener"></listener>
        </listeners>
    </suite> 

      整体来说,这就完成了接口自动化测试的一个小框架。

  • 相关阅读:
    Openjudge-NOI题库-简单算术表达式求值
    洛谷-求同构数的个数-NOIP2013提高组复赛
    洛谷-拼数-NOIP1998提高组复赛
    洛谷-统计数字-NOIP2007提高组复赛
    洛谷-谁拿了最多奖学金-NOIP2005提高组复赛
    Openjudge-NOI题库-字符串移位包含问题
    洛谷-乘积最大-NOIP2000提高组复赛
    NOIP2015-普及组复赛-第一题-金币
    Tyvj-超级书架
    Openjudge-NOI题库-出书最多
  • 原文地址:https://www.cnblogs.com/generalli2019/p/12955952.html
Copyright © 2011-2022 走看看