1.使用Java开展接口自动化需要准备的pom:

  1. <dependency>
  2. <groupId>org.apache.httpcomponents</groupId>
  3. <artifactId>httpclient</artifactId>
  4. <version>4.5.10</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.testng</groupId>
  8. <artifactId>testng</artifactId>
  9. <version>6.14.3</version>
  10. <scope>test</scope>
  11. </dependency>

2.下面开始封装Get和Post请求:

2.1因为使用到了log4j加日志,以及将参数转为JSON格式,下面是我们需要的pom依赖:

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

2.2log4j.properties配置文件详情(需要注意:log4j.properties文件需要放resources目录下,不然运行时会找不到文件不能初始化):

2.2(1)可能会有小伙伴想问为什么要加日志?
解答:1.在代码中生成日志有助于我们复盘,运行代码时,记录关键的数据。
2.实时查看代码的运行状态,如果运行已结束,错过运行时监管,事后通过log文件查看重要的运行数据及状态。

#根logger设置
log4j.rootLogger = INFO,console,file

###输出信息到控制台###
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern = [%p] %d{yyyy-MM-dd HH:mm:ss} method: %l -> %m%n

###输出 INFO 级别以上日志文件设置###
log4j.appender.file = org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.File = target/apitest.log
log4j.appender.file.Append = true
log4j.appender.file.layout = org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} method: %l - [ %p ] -> %m%n

Get:


    /**
     *get请求
     * @param url 接口请求地址
     * @param params 请求参数
     * @return      
     */
    public static String doGet(String url, Map <String, Object> params){
        log.info("开始调用doGet(),此方法用于发起get请求");
        int number = 1;  
        for (Map.Entry<String ,Object> entry : params.entrySet()) {
            //如果number=1,那就代表再url与参数的拼接处,以"?"拼接
            url += (number == 1) ? ("?" + entry.getKey() + "=" + entry.getValue()) : ("&" + entry.getKey() + "=" + entry.getValue());
            log.info("当前请求url和params:" + url);
            number++;
        }
        //指定接口请求方式
        HttpGet httpGet = new HttpGet(url);
        //设置请求头
        httpGet.addHeader("Content-Type","application/x-www-form-urlencoded;charset=UTF-8");  
        addCookieInRequestHeaderBeforeRequest(httpGet);
        CloseableHttpResponse response = null;
        CloseableHttpClient client = null;
        String result = "";
        try {
            //发送请求获取响应数据实体
            client = HttpClients.createDefault();
            //将获取到得数据,添加到请求头
            addCookieInRequestHeaderBeforeRequest(httpGet);
            response = client.execute(httpGet);
            //获取http状态码
            int statusCode = response.getStatusLine().getStatusCode();
            if (!("200".equals(String.valueOf(statusCode)))) {
                log.warn("请求失败,请检查请求参数及请求头!");
            }
            //获取http响应实体
            result = EntityUtils.toString(response.getEntity());
            log.info("状态码[" + statusCode + "],响应报文[" + result + "]");
            //如果result中有有token,就添加到map
            getResultToken(result);
        } catch (Exception e) {
            log.error("出现传输异常,请检查当前流!");
        } finally {
            //资源关闭
            try {
                if (client != null) {
                    log.info("关闭当前client连接");
                    client.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (response != null) {
                    log.info("关闭当前response连接");
                    response.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return result;
    }

Post:

 /**
     * post请求
     * @param url     接口请求地址
     * @param params 接口请求参数
     * @return
     */
    @RequestWrapper()
    public static String doPost(String url, Map<String, Object> params) {
        log.info("开始调用doPost(),此方法用于发起Post请求");
        //指定请求方式
        HttpPost httpPost = new HttpPost(url);
        //准备测试数据
        List<BasicNameValuePair> paramenters = new ArrayList<>();
        //指定请求头
        httpPost.addHeader("User-agent","Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.66 Safari/537.36");
        httpPost.addHeader("Content-Type","application/x-www-form-urlencoded;charset=UTF-8");
        //根据获取得数据,添加请求头(方法将在下面以代码块形式贴出)
        addCookieInRequestHeaderBeforeRequest(httpPost);
         //1.为什么是使用Object而不是String?
        //因为我们要处理的参数肯定不是一种类型。
        if (params != null) {
            for (Map.Entry<String, Object> entry : params.entrySet()) {
                 paramenters.add(new BasicNameValuePair(entry.getKey(),entry.getValue() + ""));
            }
        }
        //将参数转为json格式
        //JSONObject jsonObject = new JSONObject(params);
        //将参数编码格式设置为UTF-8
        //StringEntity stringEntity = new StringEntity(jsonObject.toString(),"UTF-8");
        CloseableHttpResponse response = null;
        CloseableHttpClient client = null;
        String result = null;
        try {
            //httpPost.setEntity(stringEntity);
            httpPost.setEntity(new UrlEncodedFormEntity(paramenters, "UTF-8"));
            client = HttpClients.createDefault();
            //发起请求,获取接口响应信息(状态码,响应报文,或某些特殊的响应头数据)
            response = client.execute(httpPost);
            log.info("请求url:" + url);
            log.info("请求参数:" + jsonObject);
            //判断请求头中是否带有Set-cookie
            getAndStoreCookiesFromResponseHeader(response);
            //获取状态码
            int statusCode = response.getStatusLine().getStatusCode();
            //获取响应体
            result = EntityUtils.toString(response.getEntity());
            log.info("状态码[" + statusCode + "],响应报文[" + result + "]");
            //判断result中的返回结果是否带有token,如果有添加到map保存(方法将在下面以代码块形式贴出)
            getResultToken(result);
            if (!("200".equals(String.valueOf(statusCode)))){
                log.warn("请求失败,当前状态码:" + statusCode);
                log.warn("请求参数:" + jsonObject);
            }
        } catch (Exception e) {
            log.error("运行时异常,报错内容:" + e);
        } finally {
            //资源关闭
            try {
                if (client != null) {
                    log.info("关闭当前client连接");
                    client.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (response != null) {
                    log.info("关闭当前response连接");
                    response.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        //返回响应结果
        return result;
    }

2.2代码中调用了从result获取token的方法,并将获取到的token添加到请求头。

2.2.1getResultToken():需要提前声明一个map用于保存获取的token

 public static Map<String, String> token = new HashMap<>();  //储存token的集合
  /**
     * 拆分获取response的返回结果,将token添加到map
     * @param result  Http请求返回result结果
     */
    public static void getResultToken(String result) {
        log.info("开始调用getResultValueLoad(),此方法用于获取result中的token");
        //将result转为JSONObject
        JSONObject jsonObject = JSONObject.parseObject(result);
        try {
            //取出key为data的数据
            Object data = jsonObject.get("data");
            //data不为空则转型为map
            if (data != null && data.toString().trim().length() > 0) {
                //getStringToMap():用于String转map,以代码块形式在下面贴出
                Map<String, String> stringToMap = getStringToMap(String.valueOf(data));
                for (String name : stringToMap.keySet()) {
                    String value = stringToMap.get(name);
                    if ("\"token\"".equalsIgnoreCase(name)) {
                        log.info("将当前token:" + value + "并添加到map储存");
                        token.put(name, value);
                    }
                }
            }
         //如果result中没有包含data实体,调用data.toString().trim().length(),可能会引发空指针,所以使用了try/catch
        }catch (NullPointerException e) {
            log.error("空指针异常,请检查result中是否包含data",e);
        }
    }

2.2.2getStringToMap():此方法用于String转map

    /**
     * String转map
     * @param str
     * @return
     */
    public static Map<String,String> getStringToMap(String str){
        log.info("开始调用getStringToMap(),此方法用于String转map");
        Map<String,String> map = null;
        if (str != null && str.trim().length() > 0) {
            //根据逗号截取字符串数组
            String[] str1 = str.split(",");
            //创建Map对象
            map = new HashMap<>();
            //循环加入map集合
            for (int i = 0; i < str1.length; i++) {
                //根据":"截取字符串数组
                try {
                    String[] str2 = str1[i].split(":");
                    //str2[0]为KEY,str2[1]为值
                    map.put(str2[0], str2[1]);
                } catch (ArrayIndexOutOfBoundsException e) {
                    log.error("请检查传入参数:"+str+",出现索引越界!");
                    return null;
                }
            }
        }
        return map;
    }

2.2.3addCookieInRequestHeaderBeforeRequest():如果map中储存有token,则添加至请求头

 /**
     * 判断map中是否存有值,如果不为空设置为请求头
     * @param request http请求返回得结果
     */
    private static void addCookieInRequestHeaderBeforeRequest(HttpRequest request) {
        log.info("开始调用addCookieInRequestHeaderBeforeRequest(),此方法用于添加请求头");
        String getToken = HttpUtil.token.get("token");
          if (getToken != null) {
            log.info("添加token到请求头");
            request.addHeader("token",getToken);
        }
    }

3.下面开始准备测试数据:

3.1编写测试用例,如下:

     @Test
    public void test_GetList() {
        String url = "http://hd215.api.yesapi.cn/?s=App.User.GetList";
        Map<String, Object> params = new HashMap<>();
        //这里的key不方便展示,有需要的同学可以自行去官网获取
        params.put("app_key","********");
        params.put("page",1);
        params.put("perpage",20);
        doPost(url,params);
    }

4.测试结果展示,需要修改或添加如下配置:

4.1TestNG原生reportNG测试报告:

4.1.1需要在idea修改Run—>Edit Configurations—>TestNGListenrs勾选Use default reporters选项:

image.png

4.1.2需要的pom:

 <dependencies>    
        <!--testNG报告依赖包-->
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>6.14.3</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <!-- 添加reportNG依赖 -->
        <dependency>
            <groupId>org.uncommons</groupId>
            <artifactId>reportng</artifactId>
            <version>1.1.2</version>
            <scope>test</scope>
            <!-- 排除testNG依赖 -->
            <exclusions>
                <exclusion>
                    <groupId>org.testng</groupId>
                    <artifactId>testng</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.google.inject</groupId>
            <artifactId>guice</artifactId>
            <version>3.0</version>
            <scope>test</scope>
        </dependency>
 </dependencies>

  <!--Test.xml路径-->
    <properties>
        <xmlFileName>test.xml</xmlFileName>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.19</version>
                <configuration>
                    <skipTests>false</skipTests>
                    <suiteXmlFiles>
                        <suiteXmlFile>${xmlFileName}</suiteXmlFile>
                    </suiteXmlFiles>
                    <properties>
                        <property>
                            <name>usedefaultlisteners</name>
                            <value>false</value>
                        </property>
                        <property>
                            <name>listener</name>
                            <value>org.uncommons.reportng.HTMLReporter,org.uncommons.reportng.JUnitXMLReporter</value>
                        </property>
                    </properties>
                    <forkMode>always</forkMode>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

4.1.2配置xml(可以通过xml执行多条测试用例):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="All Test Suite" preserve-order="true"  allow-return-values="true">

    <listeners>
        //reportng需要指定监听器路径
           <listener class-name="org.uncommons.reportng.HTMLReporter"/>
        <listener class-name="org.uncommons.reportng.JUnitXMLReporter"/>
    </listeners>

    <test name="查询会员分组">
        <classes>
            <class name="com.autotest.cases.UserGetListTest"></class>
        </classes>
    </test>
</suite>

4.1.3最后在项目中找到test-output—>html—>index.html,鼠标右击选择Open in Browser,用浏览器打开我们的测试报告(因为我这里使用了TestNG注解:@DataProvider数据提供者,运行了所有继承类所以抛出错误没有提供数据,大家可以忽略跳过这里):

image.png

4.2生成ExtentIReporter版美化测试报告:

4.2.1需要一个ExtentTestNGIReporterListene类,代码如下:

package com.autotest.config;

import com.aventstack.extentreports.ExtentReports;
import com.aventstack.extentreports.ExtentTest;
import com.aventstack.extentreports.ResourceCDN;
import com.aventstack.extentreports.Status;
import com.aventstack.extentreports.model.TestAttribute;
import com.aventstack.extentreports.reporter.ExtentHtmlReporter;
import com.aventstack.extentreports.reporter.configuration.ChartLocation;
import com.aventstack.extentreports.reporter.configuration.Theme;
import org.testng.*;
import org.testng.xml.XmlSuite;

import java.io.File;
import java.util.*;

public class ExtentTestNGIReporterListener implements IReporter {
    //生成的路径以及文件名
    private static final String OUTPUT_FOLDER = "test-output/";
    private static final String FILE_NAME = "apiAutoTest_report.html";

    private ExtentReports extent;

    @Override
    public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites, String outputDirectory) {
        init();
        boolean createSuiteNode = false;
        if(suites.size()>1){
            createSuiteNode=true;
        }
        for (ISuite suite : suites) {
            Map<String, ISuiteResult> result = suite.getResults();
            //如果suite里面没有任何用例,直接跳过,不在报告里生成
            if(result.size()==0){
                continue;
            }
            //统计suite下的成功、失败、跳过的总用例数
            int suiteFailSize=0;
            int suitePassSize=0;
            int suiteSkipSize=0;
            ExtentTest suiteTest=null;
            //存在多个suite的情况下,在报告中将同一个一个suite的测试结果归为一类,创建一级节点。
            if(createSuiteNode){
                suiteTest = extent.createTest(suite.getName()).assignCategory(suite.getName());
            }
            boolean createSuiteResultNode = false;
            if(result.size()>1){
                createSuiteResultNode=true;
            }
            for (ISuiteResult r : result.values()) {
                ExtentTest resultNode;
                ITestContext context = r.getTestContext();
                if(createSuiteResultNode){
                    //没有创建suite的情况下,将在SuiteResult的创建为一级节点,否则创建为suite的一个子节点。
                    if( null == suiteTest){
                        resultNode = extent.createTest(r.getTestContext().getName());
                    }else{
                        resultNode = suiteTest.createNode(r.getTestContext().getName());
                    }
                }else{
                    resultNode = suiteTest;
                }
                if(resultNode != null){
                    resultNode.getModel().setName(suite.getName()+" : "+r.getTestContext().getName());
                    if(resultNode.getModel().hasCategory()){
                        resultNode.assignCategory(r.getTestContext().getName());
                    }else{
                        resultNode.assignCategory(suite.getName(),r.getTestContext().getName());
                    }
                    resultNode.getModel().setStartTime(r.getTestContext().getStartDate());
                    resultNode.getModel().setEndTime(r.getTestContext().getEndDate());
                    //统计SuiteResult下的数据
                    int passSize = r.getTestContext().getPassedTests().size();
                    int failSize = r.getTestContext().getFailedTests().size();
                    int skipSize = r.getTestContext().getSkippedTests().size();
                    suitePassSize += passSize;
                    suiteFailSize += failSize;
                    suiteSkipSize += skipSize;
                    if(failSize>0){
                        resultNode.getModel().setStatus(Status.FAIL);
                    }
                    resultNode.getModel().setDescription(String.format("Pass: %s ; Fail: %s ; Skip: %s ;",passSize,failSize,skipSize));
                }
                buildTestNodes(resultNode,context.getFailedTests(), Status.FAIL);
                buildTestNodes(resultNode,context.getSkippedTests(), Status.SKIP);
                buildTestNodes(resultNode,context.getPassedTests(), Status.PASS);
            }
            if(suiteTest!= null){
                suiteTest.getModel().setDescription(String.format("Pass: %s ; Fail: %s ; Skip: %s ;",suitePassSize,suiteFailSize,suiteSkipSize));
                if(suiteFailSize>0){
                    suiteTest.getModel().setStatus(Status.FAIL);
                }
            }

        }
//        for (String s : Reporter.getOutput()) {
//            extent.setTestRunnerOutput(s);
//        }

        extent.flush();
    }

    private void init() {
        //文件夹不存在的话进行创建
        File reportDir= new File(OUTPUT_FOLDER);
        if(!reportDir.exists()&& !reportDir .isDirectory()){
            reportDir.mkdir();
        }
        ExtentHtmlReporter htmlReporter = new ExtentHtmlReporter(OUTPUT_FOLDER + FILE_NAME);
        // 设置静态文件的DNS
        //怎么样解决cdn.rawgit.com访问不了的情况
        htmlReporter.config().setResourceCDN(ResourceCDN.EXTENTREPORTS);

        htmlReporter.config().setDocumentTitle("接口自动化测试报告");
        htmlReporter.config().setReportName("Kinfe-apiAutoTest_report");
        htmlReporter.config().setChartVisibilityOnOpen(true);
        htmlReporter.config().setTestViewChartLocation(ChartLocation.TOP);
        htmlReporter.config().setTheme(Theme.STANDARD);
        htmlReporter.config().setCSS(".node.level-1  ul{ display:none;} .node.level-1.active ul{display:block;}");
        extent = new ExtentReports();
        extent.attachReporter(htmlReporter);
        extent.setReportUsesManualConfiguration(true);
    }

    private void buildTestNodes(ExtentTest extenttest, IResultMap tests, Status status) {
        //存在父节点时,获取父节点的标签
        String[] categories=new String[0];
        if(extenttest != null ){
            List<TestAttribute> categoryList = extenttest.getModel().getCategoryContext().getAll();
            categories = new String[categoryList.size()];
            for(int index=0;index<categoryList.size();index++){
                categories[index] = categoryList.get(index).getName();
            }
        }

        ExtentTest test;

        if (tests.size() > 0) {
            //调整用例排序,按时间排序
            Set<ITestResult> treeSet = new TreeSet<ITestResult>(new Comparator<ITestResult>() {
                @Override
                public int compare(ITestResult o1, ITestResult o2) {
                    return o1.getStartMillis()<o2.getStartMillis()?-1:1;
                }
            });
            treeSet.addAll(tests.getAllResults());
            for (ITestResult result : treeSet) {

                String name="";
                //如果有参数,则使用参数的toString组合代替报告中的name
                /*Object[] parameters = result.getParameters();
                for(Object param:parameters){
                    name+=param.toString();
                }*/
                if(name.length()>0){
                    if(name.length()>50){
                        name= name.substring(0,49)+"...";
                    }
                }else{
                    name = result.getMethod().getMethodName()/*+"---"+result.getMethod().getDescription()*/;
                }
                if(extenttest==null){
                    test = extent.createTest(name);
                }else{
                    //作为子节点进行创建时,设置同父节点的标签一致,便于报告检索。
                    test = extenttest.createNode(name).assignCategory(categories);
                }
                //test.getModel().setDescription(description.toString());
                //test = extent.createTest(result.getMethod().getMethodName());
                for (String group : result.getMethod().getGroups())
                    test.assignCategory(group);

                List<String> outputList = Reporter.getOutput(result);
                for(String output:outputList){
                    //将用例的log输出报告中
                    test.debug(output);
                }
                if (result.getThrowable() != null) {
                    test.log(status, result.getThrowable());
                }
                else {
                    test.log(status, "Test " + status.toString().toLowerCase() + "ed");
                }

                test.getModel().setStartTime(getTime(result.getStartMillis()));
                test.getModel().setEndTime(getTime(result.getEndMillis()));
            }
        }
    }

    private Date getTime(long millis) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(millis);
        return calendar.getTime();
    }
}

4.2.2xml中添加监听路径:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="Kinfe is apiAutoTest" preserve-order="true"  allow-return-values="true">
    <listeners>       
        <listener class-name="com.autotest.config.ExtentTestNGIReporterListener"/>
    </listeners>
</suite>

4.2.3pom中需要添加的依赖:

<dependency>
            <groupId>com.relevantcodes</groupId>
            <artifactId>extentreports</artifactId>
            <version>2.41.1</version>
        </dependency>
        <dependency>
            <groupId>com.vimalselvam</groupId>
            <artifactId>testng-extentsreport</artifactId>
            <version>1.3.1</version>
        </dependency>
        <dependency>
            <groupId>com.aventstack</groupId>
            <artifactId>extentreports</artifactId>
            <version>3.0.6</version>
        </dependency>
   </dependency>

4.2.4最后生成的报告html文件(超级好看的报告插件完美兼容TestNG:

注:因为我写了用例所以跑的报告内容会与上面不一致,按照步骤添加,你也能生成帅帅的报告!QQ图片20200122173221.jpg