dhruowjudgb.jpg
前言:
初级测试入门自动化启蒙,初级升中级可以说自动化是必备技能了,而接口自动化尤为吃香!
欢迎大家和我一起技术探讨,可以加我QQ一起交流:2538699146

技术栈:
1.项目构建选用Maven;
2.接口测试数据处理,选用:1.YAML,Json格式文件;
3.接口驱动,单元测试框架选用:Rest-Assured + TestNG;
4.report测试报告选用:Allure2;
5.记录执行步骤,日志框架:log4j;
6.数据库校验结果集:MySQL;
7.邮件发送:Javax.mail;
废话不多说,下面我们开始!

1.先构建一个maven项目,加入pom依赖:

  1. <dependencies>
  2. <!-- https://mvnrepository.com/artifact/org.testng/testng -->
  3. <dependency>
  4. <groupId>org.testng</groupId>
  5. <artifactId>testng</artifactId>
  6. <version>6.14.3</version>
  7. </dependency>
  8. <!-- https://mvnrepository.com/artifact/io.rest-assured/rest-assured -->
  9. <dependency>
  10. <groupId>io.rest-assured</groupId>
  11. <artifactId>rest-assured</artifactId>
  12. <version>3.3.0</version>
  13. </dependency>
  14. </dependencies>

2.打开企业微信,进入API文档,点击服务端API,在通讯录接口中寻找几个简单的API开始小动作

image.png

2.1这里我们可以看见,想肆意玩弄他们接口必须要先获取access_token,下面我们来操作一波哈:

image.png 1).先看API文档了解需要传什么字段,这里我们看到必传corpid,corpsecret;
image.png
2).corpid获取:我的企业-企业信息,有个企业id,获取该字段;
image.png
3).corpsecret获取:管理工具-通讯录同步-开启API调试,拿到Secret;
image.png
image.png

2.2打开idea在我们刚创建的项目中开始小实验哈,创建一个class,开始实现获取access_token,代码如下:

package cn.Knife.Wework;

import io.restassured.RestAssured;
import io.restassured.response.Response;

/**
 * @author Knife
 * @description
 * @createTime 2020-05-07 15:30
 */
public class getAccess_token {

    /**
     * 获取wework的access_token
     * @param corpid  企业id
     * @param corpsecret    应用密钥
     * @return
     */
    public Response getToken(String corpid, String corpsecret) {
        Response response = RestAssured.given()
                .log().all() //打印请求头信息
                .queryParam("corpid", corpid)
                .queryParam("corpsecret", corpsecret)
                .when().get("https://qyapi.weixin.qq.com/cgi-bin/gettoken") //发送get请求
                .then().extract().response();
        return response;
    }

}

2.2.1封装好接口请求方法,下面我们来快乐的调用:

1).鼠标右击,选择Generate,如下:
image.png
2).Testing library:选择testng,勾选我们需要创建的方法,如下:
image.png
3).这里讲解一下文档注释的好处哈,当我们不知道要传递什么值,又或者值太多,我们可以通过文档注释方便我们调用,效果如下:
image.png
4).废话不多说,我们开始实测试,代码如下:


import org.testng.annotations.Test;

import static org.hamcrest.Matchers.equalTo;


/**
 * @author Knife
 * @description
 * @createTime 2020-05-07 15:50
 */
public class GetAccess_tokenTest {


    @Test(description = "获取token")
    public void testGetToken() {
        //获取一个GetAccess_token实例对象,才能调用它的方法
        GetAccess_token getAccess_token = new GetAccess_token();
        String corpid = "ww2df66b08696343ff";
        String corpsecret  = "vj1C5akNwtxZb18MECiNZym4IVBTWEi18L57r1KJF2s";
        getAccess_token.getToken(corpid,corpsecret).then() //获取响应信息
                .log().all()  //打印全部响应日志
                .statusCode(200) //断言状态码是200
                .body("errcode",equalTo(0)) //断言body中的errcode字段值是0
                ;
    }
}

运行结果大家也可以看一下,比较清晰,比起client少了一些处理,结果如下:

Request method:    GET
Request URI:    https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=ww2df66b08696343ff&corpsecret=vj1C5akNwtxZb18MECiNZym4IVBTWEi18L57r1KJF2s
Proxy:            <none>
Request params:    <none>
Query params:    corpid=ww2df66b08696343ff
                corpsecret=vj1C5akNwtxZb18MECiNZym4IVBTWEi18L57r1KJF2s
Form params:    <none>
Path params:    <none>
Headers:        Accept=*/*
Cookies:        <none>
Multiparts:        <none>
Body:            <none>
HTTP/1.1 200 OK
Server: nginx
Date: Thu, 07 May 2020 08:11:02 GMT
Content-Type: application/json; charset=UTF-8
Content-Length: 277
Connection: keep-alive
Error-Code: 0
Error-Msg: ok

{
    "errcode": 0,
    "errmsg": "ok",
    "access_token": "XQntbz4sN9lyxTMbsoLwmi2sYFMeJZN-L1l52lX5NwJjhhuqbtGmnD2ubk0uo-EQVGi79zM5dDan7IFDUpSc_NPqYucD7TLjgEZl4JrFx1-TznpjOno573pmNq2mpKK7MTcSOW141Fgbp1867LtKtoRnbeKg-ZE46LhBXAzF6qPbO-72Bdx9xHAA1CqXxHweDj97Wg14UHXRvspxo54sUw",
    "expires_in": 7200
}

5)扩展一下,正常场景有了,异常也让大家看看哈,在一个上面那个类添加一个方法,代码如下:

    @Test(description = "获取token异常测试")
    public void errGetToken() {
        //获取一个GetAccess_token实例对象,才能调用它的方法
        GetAccess_token getAccess_token = new GetAccess_token();
        String corpid = "ww2df66b08696343ff";
        String corpsecret = "vj1C5akNwtxZb18MECiNZym4IVBTWEi18L57r1KJF2s";
        //corpid为空
        getAccess_token.getToken("", corpsecret).then()
                .log().all()
                .statusCode(200)
                .body("errcode", equalTo(0)); //这里断言失败,因为corpid为空
    }

运行结果:

Request method:    GET
Request URI:    https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=&corpsecret=vj1C5akNwtxZb18MECiNZym4IVBTWEi18L57r1KJF2s
Proxy:            <none>
Request params:    <none>
Query params:    corpid=
                corpsecret=vj1C5akNwtxZb18MECiNZym4IVBTWEi18L57r1KJF2s
Form params:    <none>
Path params:    <none>
Headers:        Accept=*/*
Cookies:        <none>
Multiparts:        <none>
Body:            <none>
HTTP/1.1 200 OK
Server: nginx
Date: Thu, 07 May 2020 08:22:30 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 43
Connection: keep-alive
Cache-Control: no-cache
Pragma: no-cache

{
    "errcode": 41002,
    "errmsg": "corpid missing"
}



java.lang.AssertionError: 1 expectation failed.
JSON path errcode doesn't match.
Expected: <0>        //看这里看这里,这是预期body断言结果
  Actual: 41002        //实际body响应errcode结果

2.3拿到access_token之后,我们终于能肆意的对企业微信API进行骚操作,因为入门教程哈,所以我们选选择几个简单的API进行操作,选择部门管理、如下:

image.png

2.3.1先给大家看看他们的API描述和文档,如下:

image.pngimage.png
image.pngimage.pngimage.png
image.png
1).这里需要用到马爸爸的fastjson框架进行序列化,加入pom依赖:

                 <!-- fastjson依赖 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </dependency>

2).废话不多说,开始上第一版代码:

import com.alibaba.fastjson.JSONObject;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import io.restassured.response.Response;

import java.util.HashMap;

/**
 * @author Knife
 * @description 部门管理
 * @createTime 2020-05-07 16:45
 */
public class Department {

    //定义存储token的容器
    private static String[] token;

    /**
     * 因为我个人比较懒,这里直接用一个String数组,返回要获取的token
     * 单例设计,这里采用懒汉式
     *
     * @return
     */
    private static String[] getAcc_token() {
        if (token == null) {
            GetAccess_token getAccess_token = new GetAccess_token();
            Response response = getAccess_token.getToken("ww2df66b08696343ff", "vj1C5akNwtxZb18MECiNZym4IVBTWEi18L57r1KJF2s");
            String t = response.path("access_token").toString();
            token = new String[]{"access_token", t};
        }
        return token;
    }

    /**
     * 创建部门
     *
     * @param name     部门名称
     * @param name_en  别名
     * @param parentid 父类id
     * @param order    排序
     * @param id       id
     */
    public Response create(String name, String name_en, String parentid, String order, String id) {
        //将参数都添加到map
        HashMap<String, Object> parame = new HashMap();
        parame.put("name", name);
        parame.put("name_en", name_en);
        parame.put("parentid", parentid);
        parame.put("order", order);
        parame.put("id", id);

        //将map序列化成一个对象
        String body = JSON.toJSONString(parame);
        return RestAssured.given()
                .log().all()
                .contentType(ContentType.JSON) //设置ContentType为json,在client中发送请求,需要将参数转为StringEntity同理
                .queryParam(getAcc_token()[0], getAcc_token()[1])  //queryParam拼接参数在url
                .body(body)  //body中放入请求体
                .when().post("https://qyapi.weixin.qq.com/cgi-bin/department/create")
                .then().log().all().extract().response();
    }


    /**
     * 更新部门
     *
     * @param name     部门名称
     * @param name_en  别名
     * @param parentid 父类id
     * @param order    排序
     * @return
     */
    public Response update(String name, String name_en, String parentid, String order, String id) {
        //将参数都添加到map
        HashMap<String, Object> parame = new HashMap();
        parame.put("name", name);
        parame.put("name_en", name_en);
        parame.put("parentid", parentid);
        parame.put("order", order);
        parame.put("id", id);
        //将map序列化成一个对象
        String body = JSON.toJSONString(parame);
        return RestAssured.given()
                .log().all()
                .contentType(ContentType.JSON)
                .queryParam(getAcc_token()[0], getAcc_token()[1])
                .body(body)
                .when().post("https://qyapi.weixin.qq.com/cgi-bin/department/update")
                .then()
                .log().all()
                .extract().response();
    }

    /**
     * 删除部门
     *
     * @param id 部门id
     * @return
     */
    public Response delete(String id) {
        return RestAssured.given()
                .log().all()
                .queryParam(getAcc_token()[0], getAcc_token()[1])
                .queryParam("id", id)
                .when().get("https://qyapi.weixin.qq.com/cgi-bin/department/delete")
                .then()
                .log().all()
                .extract().response();
    }

    /**
     * 获取部门列表
     *
     * @param id 部门id
     * @return
     */
    public Response list(String id) {
        return RestAssured.given()
                .log().all()
                .queryParam(getAcc_token()[0], getAcc_token()[1])
                .queryParam("id", id)
                .when().get("https://qyapi.weixin.qq.com/cgi-bin/department/list")
                .then()


                .log().all()
                .extract().response();
    }
}

2).开始调式我们调皮的小代码,生成case,如下:
注:Rest-Assured官方文档,中文版:https://github.com/RookieTester/rest-assured-doc

package cn.Knife.Wework;
import org.testng.annotations.Test;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItems;

/**
 * @author Knife
 * @description
 * @createTime 2020-05-07 17:52
 */
public class DepartmentTest {

    private static Department department = new Department();

    //priority控制执行顺序吗,数值越小越靠前
    @Test(description = "创建部门测试",priority = 1)
    public void testCreate() {
        department.create("高级测试部门","SUPER_Test","1","1","100")
                .then().statusCode(200).body("errcode",equalTo(0));

        department.create("高级测试部门1","SUPER_Test2","1","1","101")
                .then().statusCode(200).body("errcode",equalTo(0));
    }

    @Test(description = "修改部门测试",priority = 2)
    public void testUpdate() {
        department.update("更新高级测试部门","SUPER_Update","1","1000","100")
                .then().statusCode(200).body("errcode",equalTo(0));
    }

    @Test(description = "获取部门列表",priority = 3)
    public void testList() {
        //我知道你们在想有没有断言多个参数,rest-assured它支持多个参数断言,同时也能断言多个字段
        department.list("").then().statusCode(200)
                //因为我刚开始已经创建一个了,所以这里断言三个部门
                .body("department.name",hasItems("Knife","更新高级测试部门","高级测试部门1"))
        .body("errcode",equalTo(0));

        department.list("1").then()
                .body("department.id",hasItems(1,100,101))
                .body("errcode",equalTo(0));

        department.list("").then().statusCode(200)
                //引用官方原话:注意这里的"json path"语法使用的是Groovy的GPath标注法,不要和Jayway的JsonPath语法混淆
                .body("department.find { it.id==100 }.name",equalTo("更新高级测试部门"));
    }

    @Test(description = "删除部门测试",priority = 4)
    public void testDelete() {
        department.delete("100").then().statusCode(200).body("errcode",equalTo(0));
        department.delete("101").then().statusCode(200).body("errcode",equalTo(0));
    }
}

运行结果如下:
image.png

2.4基础使用没问题了,我们开始对代码进行改造,如下:

2.4.1加入yaml模板对象存储数据,需要的pom依赖:

注释:yaml使用教程http://www.ruanyifeng.com/blog/2016/07/yaml.html

          <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml -->
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-yaml</artifactId>
            <version>2.10.1</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
            <version>2.10.1</version>
        </dependency>

1).先用yaml将我们的登录数据拎出来,但是该怎么操作yaml呢??? 没关系我也不会,所以我直接写了个工具类,新建一个类,我们取名是Super_Utils,以后要加的功能都继承与他,应用page Object思想代码隔离,就算业务改变,也不用大改代码,废话不多说了上代码:

package cn.Knife.Wework.Utils;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.Objects;

/**
 * @author Knife
 * @description
 * @createTime 2020-05-07 21:08
 */
public class Super_Utils {

    //用于拼接path,此路径为resources根路径
    private static final String RESOURCES_PATH = "src/main/resources/";

     /**
     * @param path yaml文件路径
     * @param type 0:代表写入,1:代表读取
     * @param obj  接收或写入的对象
     * @return
     */
    @org.jetbrains.annotations.Nullable
    public static Object YamlFactoryIsReadYamlAndWriterYaml(String path, String type, Object obj) {
        ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
        if (Objects.nonNull(type)) {
            try {
                if ("0".equals(type)) {
                    writerYaml(objectMapper, path, obj);
                }
                if ("1".equals(type)) {
                    //返回读取的yaml数据,序列化成对象
                    return readYamlValue(objectMapper, path, obj);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        } 
        return null;
    }

    /**
     * 读取yaml文件数据
     *
     * @param objectMapper
     * @param path         resources下文件路径
     * @param obj          读取yaml数据的对象实体
     * @return
     */
    public static Object readYamlValue(ObjectMapper objectMapper, String path, Object obj) {
        if (Objects.nonNull(path)) {
            try {
                return objectMapper.readValue(Super_Utils.class.getResourceAsStream(path), obj.getClass());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * 传一个对象,写入内容到yaml
     *
     * @param objectMapper
     * @param path         resources下文件路径
     * @param obj          写入内容的对象
     */
    public static void writerYaml(ObjectMapper objectMapper, String path, Object obj) {
        if (Objects.nonNull(path) && Objects.nonNull(obj)) {
            //定义resource的路径,避免写的时候写入到target\classes下面
            path = RESOURCES_PATH + path;
            //获取一个输出流对象,指定写入文件路径
            try (OutputStream os = new FileOutputStream(new File(path))) {
                //写入对象到yaml文件
                objectMapper.writeValue(os, obj);
                //打印写入yaml的数据
                System.out.println("写入内容如下:");
                System.out.println("***********************************************************************");
                System.out.println(objectMapper.writeValueAsString(obj));
                System.out.println("***********************************************************************");
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            System.out.println(Objects.nonNull(path) ?
                    "当前写入yaml路径:" + path : "当前yaml路径为空!");
            System.out.println(Objects.nonNull(obj) ?
                    "当前写入内容:" + obj.toString() : "请检查传的obj,或当前写入内容为空!");
            throw new RuntimeException("ERR_PATH_OR_OBJ == NULL");
        }
    }
}

2).开始对我们写的代码一顿小操作,先创建一个wework类,管理登录数据,方便corpid和corpsecret变动管理:

/**
 * @author Knife
 * @description
 * @createTime 2020-05-07 21:18
 */
public class WeworkConfig {

    public String corpid = "ww2df66b08696343ff";
    public String corpsecret = "vj1C5akNwtxZb18MECiNZym4IVBTWEi18L57r1KJF2s";

}

3).在resources目录下新建conf文件夹, 新建一个weworkconfig.yaml文件:
image.png
4).通过Generate生成case,开始进行写入和读取测试,如下:

/**
 * @author Knife
 * @description
 * @createTime 2020-05-07 21:22
 */
public class WeworkConfigTest {

    private static Super_Utils super_utils = new Super_Utils();

    @Test
    public void yamlTest() {
        WeworkConfig weworkConfig = new WeworkConfig();
        super_utils.YamlFactoryIsReadYamlAndWriterYaml("/conf/WeworkConfig.yaml","0",weworkConfig);
    }

}

运行结果:
image.png
5).我们多造点数据写入试试,不同数据类型,如下:


    @Test
    public void yamlTest() {
        WeworkConfig weworkConfig = new WeworkConfig();
        weworkConfig.hashMap = new HashMap<String, Object>() {{
            put("desc","写入yaml测试");
            put("caseid",1);
            put("type",0);
            put("tttt","sssss");
        }};
        weworkConfig.list = new ArrayList<String>(){
            {
                add(weworkConfig.toString());
                add("这是写入测试");
                add("5555555");
                add("44444");
                add("33333");
                add("这222是写入测试");
                add("1111111");
            }
        };
        weworkConfig.strings = new String[]{"我","们","开","始","测","试","拉"};
        super_utils.YamlFactoryIsReadYamlAndWriterYaml("conf/WeworkConfig.yaml","0",weworkConfig);
    }

查看yaml文件内容的数据格式:

---
corpid: "ww2df66b08696343ff"
corpsecret: "vj1C5akNwtxZb18MECiNZym4IVBTWEi18L57r1KJF2s"
hashMap: #map的结构
  tttt: "sssss"
  caseid: 1
  type: 0
  desc: "写入yaml测试"
list: #list在yaml格式和String[]一样,因为底层就是数组
- "cn.Knife.Wework.WeworkConfig@100fc185"
- "这是写入测试"
- "5555555"
- "44444"
- "33333"
- "这222是写入测试"
- "1111111"
strings: 
- "我"
- "们"
- "开"
- "始"
- "测"
- "试"
- "拉"

2.4.2实现数据落入yaml之后,我们开始代码抽取:

1).先将我们写的好Wewrokconfig进行改造:

import cn.Knife.Wework.Utils.Super_Utils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;

import java.util.Objects;

/**
 * @author Knife
 * @description
 * @createTime 2020-05-08 0:22
 */
public class WeworkConfig extends Super_Utils {

    public String corpid = "1";
    public String corpsecret = "1";

    private static WeworkConfig weworkConfig;

    /**
     * 单例懒汉式:提供方法给外界访问,获得WeworkConfig对象
     *
     * @return
     */
    public static WeworkConfig getInstance() {
        if (weworkConfig == null) {
            weworkConfig = getWeworkConfig("/conf/WeworkConfig.yaml");
        }
        return weworkConfig;
    }

    /**
     * 返回一个WeworkConfig对象,这里是yaml模板
     *
     * @param path yaml路径
     * @return
     */
    public static WeworkConfig getWeworkConfig(String path) {
        ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
        if (Objects.nonNull(path)) {
            WeworkConfig wc = new WeworkConfig();
            return (WeworkConfig) YamlFactoryIsReadYamlAndWriterYaml(path, "1", wc);
        } else {
            throw new RuntimeException("当前文件路径为空!");
        }
    }
}

开始测试我们写的方法,步骤不说了,看代码:

import org.testng.annotations.Test;

/**
 * @author Knife
 * @description
 * @createTime 2020-05-07 21:22
 */
public class WeworkConfigTest {

    private WeworkConfig weworkConfig = new WeworkConfig();

    @Test
    public void yamlTest() {
        WeworkConfig we = weworkConfig.getWeworkConfig("/conf/WeworkConfig.yaml");
        System.out.println(we.corpid);
        System.out.println(we.corpsecret);

    }
}

输出结果如下:
image.png
2).在拎出来登录业务之前,我们先在之前写的Super_Utils中加上token[],代码如下:

   //储存token的容器
    public static String token[];

3).正儿八经开始拎出登录,继承父类,我们的super工具类,通过继承关系给token赋值:


import cn.Knife.Wework.WeworkConfig;
import io.restassured.RestAssured;

import java.util.Objects;

/**
 * @author Knife
 * @description
 * @createTime 2020-05-07 23:41
 */
public class WeworkLogin extends WeworkConfig {


    /**
     * 登录获取token
     *
     * @return
     */
    public static String getWeworkToken() {
        return RestAssured.given().log().all()
                .queryParam("corpid", WeworkConfig.getInstance().corpid)
                .queryParam("corpsecret", WeworkConfig.getInstance().corpsecret)
                .when().get("https://qyapi.weixin.qq.com/cgi-bin/gettoken")
                .then().log().all().statusCode(200)
                .extract().path("access_token");
    }

    /**
     * 返回token的key:value
     *
     * @return token[0]:token字段名,token[1]:token值
     */
    public static String[] getToken() {
        if (Objects.isNull(token)) {
            token = new String[]{"access_token", getWeworkToken()};
        }
        return token;
    }
}

4).做个小实验,通过Generate生成case,这里就不放case代码了,运行结果如下:image.png

2.4.3隔离出来登录业务之后,就能获取到token了,我们再次简化一次代码:

1).Super_Utils增加代码如下:

  /**
     * 初始化RequestSpecification对象
     *
     * @return
     */
    public  RequestSpecification getDefaultRequestSpecification() {
        return RestAssured.given();
    }

2).新建一个Rest_Perfect对象,继承Super_Utuils类,重写getDefaultRequestSpecification()方法,代码如下:

import io.restassured.http.ContentType;
import io.restassured.specification.RequestSpecification;

import static cn.Knife.Wework.AddressBookManagement.WeworkLogin.getToken;
import static io.restassured.RestAssured.given;

/**
 * @author Knife
 * @description
 * @createTime 2020-05-08 2:28
 */
public class Rest_Perfect extends Super_Utils {


    public  RequestSpecification getDefaultRequestSpecification() {
        RequestSpecification requestSpecification;
        requestSpecification = given()
                .log().all()    //打印请求头信息
                .contentType(ContentType.JSON)  //设置ContentType参数类型为json
                .queryParam(getToken()[0], getToken()[1]);  //将token携带在url
        requestSpecification.filter((req, res, ctx) -> {
            //todo: 对请求响应,做封装
            return ctx.next(req, res);
        });
        requestSpecification.then().expect().log().all().statusCode(200);   //打印响应信息,断言状态是200
        return requestSpecification;
    }
}

3).简化一下我们的Department类,代码如下:

import cn.Knife.Wework.Utils.Rest_Perfect;
import com.alibaba.fastjson.JSONObject;
import io.restassured.response.Response;

import java.util.HashMap;

/**
 * @author Knife
 * @description 部门管理
 * @createTime 2020-05-07 16:45
 */
public class Department extends Rest_Perfect {


    /**
     * 创建部门
     *
     * @param name     部门名称
     * @param name_en  别名
     * @param parentid 父类id
     * @param order    排序
     * @param id       id
     */
    public Response create(String name, String name_en, String parentid, String order, String id) {
        //将参数都添加到map
        HashMap<String, Object> parame = new HashMap();
        parame.put("name", name);
        parame.put("name_en", name_en);
        parame.put("parentid", parentid);
        parame.put("order", order);
        parame.put("id", id);

        //将map序列化成一个对象
        String body = JSON.toJSONString(parame);
        return getDefaultRequestSpecification()
                .body(body)
                .when().post("https://qyapi.weixin.qq.com/cgi-bin/department/create")
                .then().extract().response();
    }


    /**
     * 更新部门
     *
     * @param name     部门名称
     * @param name_en  别名
     * @param parentid 父类id
     * @param order    排序
     * @return
     */
    public Response update(String name, String name_en, String parentid, String order, String id) {
        //将参数都添加到map
        HashMap<String, Object> parame = new HashMap();
        parame.put("name", name);
        parame.put("name_en", name_en);
        parame.put("parentid", parentid);
        parame.put("order", order);
        parame.put("id", id);
        //将map序列化成一个对象
        String body = JSON.toJSONString(parame);
        return getDefaultRequestSpecification()
                .body(body)
                .when().post("https://qyapi.weixin.qq.com/cgi-bin/department/update")
                .then()
                .extract().response();
    }

    /**
     * 删除部门
     *
     * @param id 部门id
     * @return
     */
    public Response delete(String id) {
        return getDefaultRequestSpecification()
                .queryParam("id",id)
                .when().get("https://qyapi.weixin.qq.com/cgi-bin/department/delete")
                .then()
                .extract().response();
    }

    /**
     * 获取部门列表
     *
     * @param id 部门id
     * @return
     */
    public Response list(String id) {
        return getDefaultRequestSpecification()
                .queryParam("id", id)
                .when().get("https://qyapi.weixin.qq.com/cgi-bin/department/list")
                .then()
                .extract().response();
    }
}

重新跑一下DepartmentTest,实验一下:
image.png

2.5现在我们进行一次大改,Super_Uitils再次升级:

1).先把我们之前写的代码进行分包,如下:
image.png
2)新建一个yaml模板的实体类,用于管理测试数据,代码如下:

import java.util.HashMap;
import java.util.Objects;

/**
 * @author Knife
 * @description
 * @createTime 2020-05-08 14:19
 */
public class Rest_Info extends Super_Utils {

    //为什么把url和uri拆分维护????
    //如果后期涉及到比较强的上游接口依赖难道每次我们都先调用其他接口吗??
    //不!拿到uri判断过后就能对它搔首弄姿,肆意玩弄它
    public String url = "https://qyapi.weixin.qq.com/";
    public String uri;
    public String method;
    public HashMap<String, Object> header;
    public HashMap<String, Object> query = new HashMap<>();
    public String body;

    private static Rest_Info rest_info;

    /**
     * 如果rest_info为空则返回new一个
     * @return
     */
    public static Rest_Info getInstance() {
        return (rest_info = Objects.nonNull(rest_info) ? rest_info : new Rest_Info());
    }
}

3)在resources目录下department文件夹->create.yaml文件,写入内容,分包如下:

image.png

2.5.1做好准备工作之后,我们在Super_Utils类中加入方法getResponseFromYaml(),updateFromYamlRest_Info()方法,大改后的代码如下:

package cn.Knife.Wework.Utils;


import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import io.restassured.RestAssured;
import io.restassured.response.Response;
import io.restassured.specification.RequestSpecification;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Objects;

/**
 * @author Knife
 * @description
 * @createTime 2020-05-07 21:08
 */
public class Super_Utils {

    //储存token的容器
    public static String token[];

    //用于拼接path,此路径为resources根路径
    private static final String RESOURCES_PATH = "src/main/resources/";

    /**
     * 初始化RequestSpecification对象
     *
     * @return
     */
    public RequestSpecification getDefaultRequestSpecification() {
        return RestAssured.given();
    }


    /**
     * 通过先更新yaml模板,获取rest_info对象,取出rest_info数据对接口发起请求,返回一个response
     * 传输规范:body:"$.body",header:"$.header",使用json模板,传path:"$.file"
     *
     * @param path   yaml路径从resources开始
     * @param parame 接口请求参数:"$.header",query,"$.body"
     * @return
     */
    public Response getResponseFromYaml(String path, HashMap<String, Object> parame) {
        Rest_Info rest_info = updateFromYamlRest_Info(path, parame);
        RequestSpecification requestSpecification;
        if (Objects.nonNull(rest_info)) {

            //调用getDefaultRequestSpecification()返回一个requestSpecification对象
            requestSpecification = getDefaultRequestSpecification();
            if (Objects.nonNull(rest_info.query)) {
                rest_info.query.entrySet().forEach(
                        entry -> requestSpecification.queryParam(entry.getKey(), entry.getValue())
                );
            }

            //给requestSpecification.body赋值这里采用三元表达式,如果rest_info中body不为空则赋值为rest_info.body,反则赋值为null
            requestSpecification.body(
                    ((Objects.nonNull(rest_info.body)) ? rest_info.body : "")
            );
            return requestSpecification
                    .when()
                    .request(rest_info.method, rest_info.url + rest_info.uri)
                    .then()
                    .extract().response();
        } else {
            throw new NullPointerException("ERR_REST_INFO == NULL!");
        }
    }

    /**
     * 通过parame更新Rest_Info中的数据
     *
     * @param path
     * @param parame
     * @return
     */
    public Rest_Info updateFromYamlRest_Info(String path, HashMap<String, Object> parame) {
        ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
        Rest_Info ri = Rest_Info.getInstance();
        if (Objects.nonNull(path) && Objects.nonNull(parame)) {
            Rest_Info rest_info = (Rest_Info) readYamlValue(objectMapper, path, ri);

            //将yaml中的method取出,转换为小写,进行比较
            if ("get".equals(rest_info.method.toLowerCase())) {

                //如果method为get,将parame中的数据添加到Rest_Info的query
                parame.entrySet().forEach(
                        entry -> rest_info.query.put(entry.getKey(), entry.getValue())
                );
            }

            //如果method为post,将parame中body取出,设置为Rest_Info的body。
            //这里的"$.body",只是parame中的key,就像一种规范,用一个标识符号代表某个数据
            if ("post".equals(rest_info.method.toLowerCase())) {

                /*if (Objects.nonNull(parame.get("$.body"))) {
                    rest_info.body = parame.get("$.body").toString();
                }*/

                //如果body不为空则设置为rest_info.body
                rest_info.body = Objects.nonNull(parame.get("$.body")) ? parame.get("$.body").toString() : "";
            }

            //返回的rest_info对象,这里已经替换过query或body
            return rest_info;
        } else {
            throw new NullPointerException("ERR_PATH_OR_PARAME == NULL!");
        }
    }


    /**
     * @param path yaml文件路径
     * @param type 0:代表写入,1:代表读取
     * @param obj  接收或写入的对象
     * @return
     */
    public static Object YamlFactoryIsReadYamlAndWriterYaml(String path, String type, Object obj) {
        ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
        if (Objects.nonNull(type)) {
            try {
                if ("0".equals(type)) {
                    writerYaml(objectMapper, path, obj);
                }
                if ("1".equals(type)) {
                    //返回读取的yaml数据,序列化成对象
                    return readYamlValue(objectMapper, path, obj);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * 读取yaml文件数据
     *
     * @param objectMapper
     * @param path         resources下文件路径
     * @param obj          读取yaml数据的对象实体
     * @return
     */
    public static Object readYamlValue(ObjectMapper objectMapper, String path, Object obj) {
        if (Objects.nonNull(path)) {
            try {
                return objectMapper.readValue(Super_Utils.class.getResourceAsStream(path), obj.getClass());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * 传一个对象,写入内容到yaml
     *
     * @param objectMapper
     * @param path         resources下文件路径
     * @param obj          写入内容的对象
     */
    public static void writerYaml(ObjectMapper objectMapper, String path, Object obj) {
        if (Objects.nonNull(path) && Objects.nonNull(obj)) {
            //定义resource的路径,避免写的时候写入到target/classes下面
            path = RESOURCES_PATH + path;
            //获取一个输出流对象,指定写入文件路径
            try (OutputStream os = new FileOutputStream(new File(path))) {
                //写入对象到yaml文件
                objectMapper.writeValue(os, obj);
                //打印写入yaml的数据
                System.out.println("写入内容如下:");
                System.out.println("***********************************************************************");
                System.out.println(objectMapper.writeValueAsString(obj));
                System.out.println("***********************************************************************");
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            System.out.println(Objects.nonNull(path) ?
                    "当前写入yaml路径:" + path : "当前yaml路径为空!");
            System.out.println(Objects.nonNull(obj) ?
                    "当前写入内容:" + obj.toString() : "请检查传的obj,或当前写入内容为空!");
            throw new RuntimeException("ERR_PATH_OR_OBJ == NULL");
        }
    }
}

1).大改Super_Utils之后怎么能不测试呢,下面我们开始哈,先改造一下Department的create方法,代码如下:

 /**
     * 创建部门
     *
     * @param name     部门名称
     * @param name_en  别名
     * @param parentid 父类id
     * @param order    排序
     * @param id       id
     */
    public Response create(String name, String name_en, String parentid, String order, String id) {
        //将参数都添加到map
        HashMap<String, Object> parame = new HashMap();
        parame.put("name", name);
        parame.put("name_en", name_en);
        parame.put("parentid", parentid);
        parame.put("order", order);
        parame.put("id", id);
        //将map序列化成String
        String body = JSON.toJSONString(parame);
        return getResponseFromYaml("/department/create.yaml",new HashMap<String, Object>(){{
            put("$.body",body);
        }});
    }

DepartmentTest中代码不用改,执行结果:
image.png
2).查询企业微信code代码才知道,告诉我们部门id或名称已存在,在我们的Rest_Perfect中再加一个方法把,代码如下:

 /**
     * 获取当前系统时间
     *
     * @return
     */
    public String getSystemDate() {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
        Date date = new Date();
        return dateFormat.format(date);
    }

3).修改DepartmentTest中的create方法:

    @Test(description = "创建部门测试",priority = 1)
    public void testCreate() {
        String data = department.getSystemDate();
        department.create("高级测试部门" + data
                ,"SUPER_Test" + data,"1","1",data)
                .then().statusCode(200).body("errcode",equalTo(0));

        department.create("高级测试部门1" + data
                ,"SUPER_Test2" + data,"1","1",data.substring(8,14))
                .then().statusCode(200).body("errcode",equalTo(0));
    }

执行结果:
image.png
4).调试产生的脏乱数据需要处理,我们在Department中添加一个deleteAll()方法,用于清理产生的数据,代码如下:

 /**
     * 删除所有部门
     * @return
     */
    public Response deleteAll() {
        //是不是觉得语法很特殊,比起client加工具类处理依赖方便一些,rest直接支持jsonpath语法
        List<Object> userIdLsit = list("").path("department.id");
        //原始部门是不能删除的哈,有子部门也不能删除,部门下存在人员也不能删除
        userIdLsit.remove(0);
        //删除userIdLsit中的所有部门
        userIdLsit.forEach(
                userid -> delete(userid.toString())
        );
        return null;
    }

测试运行结果如下:
image.png
5).加了deleteAll()方法后,我们改造一下DepartmentTest,改后代码如下:

package cn.Knife.Wework;

import cn.Knife.Wework.AddressBookManagement.Department;
import org.testng.annotations.*;

import java.util.Objects;

import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItems;

/**
 * @author Knife
 * @description
 * @createTime 2020-05-07 17:52
 */
public class DepartmentTest {

    private static Department department;

    @BeforeClass
    public void setUp() {
        department = Objects.isNull(department) ? new Department() : department;
        //初始化时清理测试数据
        department.deleteAll();
    }

    //去掉priority排序,之前是执行顺序会乱,导致先删了数据,后面的case拿不到数据断言失败
    @Test(description = "创建部门测试")
    public void testCreate() {
        String data = department.getSystemDate();
        String name = "高级测试部门" + data;
        String nameTwo = "高级测试部门1" + data;
        department.create(name, "SUPER_Test" + data, "1", "1", data)
                .then().statusCode(200).body("errcode", equalTo(0));

        department.create(nameTwo, "SUPER_Test2" + data, "1", "1", data.substring(8, 14))
                .then().statusCode(200).body("errcode", equalTo(0));

        //通过list接口查询到id
        String idOne = department.list("").path("department.find { it.name == '" + name + "'}.id").toString();
        String idTwo = department.list("").path("department.find { it.name == '" + nameTwo + "'}.id").toString();

        //修改创建的部门名称和别名
        department.update("测试进行中" + data.substring(10, 14), "update" + data.substring(10, 14)
                , "1", data.substring(10, 14), idOne);
        department.update("测试进行中" + data.substring(10, 14), "update" + data.substring(10, 14)
                , "1", data.substring(10, 14), idOne);
    }

    @Test(description = "修改部门测试")
    public void testUpdate() {
        String data = department.getSystemDate();
        String name = "这是修改试部门测试" + data;
        String nameTwo = "这是修改试部门测试1" + data;
        department.create(name, "Update_Test" + data, "1", "1", data);

        department.create(nameTwo, "Update_Test2" + data, "1", "1", data.substring(8, 14));

        //通过list接口查询到id
        String idOne = department.list("").path("department.find { it.name == '" + name + "'}.id").toString();
        String idTwo = department.list("").path("department.find { it.name == '" + nameTwo + "'}.id").toString();

        //修改创建的部门名称和别名
        department.update("这是修改测试" + data.substring(10, 14), "this is update" + data.substring(10, 14)
                , "1", data.substring(10, 14), idOne);
        department.update("这是修改测试2" + data.substring(10, 14), "this is update2" + data.substring(10, 14)
                , "1", data.substring(10, 14), idOne);
    }

    //@Test(description = "获取部门列表")
    public void testList() {

        //我知道你们在想有没有断言多个参数,rest-assured它支持多个参数断言,同时也能断言多个字段
        department.list("").then().statusCode(200)
                //因为我刚开始已经创建一个了,所以这里断言三个部门
                .body("department.name", hasItems("Knife", "更新高级测试部门", "高级测试部门1"))
                .body("errcode", equalTo(0));

        department.list("1").then()
                .body("department.id", hasItems(1, 100, 101))
                .body("errcode", equalTo(0));

        department.list("").then().statusCode(200)
                //引用官方原话:注意这里的"json path"语法使用的是Groovy的GPath标注法,不要和Jayway的JsonPath语法混淆
                .body("department.find { it.id==100 }.name", equalTo("更新高级测试部门"));
    }

    @Test(description = "删除部门测试")
    public void testDelete() {
        String data = department.getSystemDate();
        String name = "删除部门测试" + data;
        String nameTwo = "删除部门测试1" + data;
        department.create(name, "delete_Test" + data, "1", "1", data);

        department.create(nameTwo, "delete_Test2" + data, "1", "1", data.substring(8, 14));

        //通过list接口查询到id
        String idOne = department.list("").path("department.find { it.name == '" + name + "'}.id").toString();
        String idTwo = department.list("").path("department.find { it.name == '" + nameTwo + "'}.id").toString();

        //修改创建的部门名称和别名
        department.update("这是删除测试" + data.substring(10, 14), "this is delete" + data.substring(10, 14)
                , "1", data.substring(10, 14), idOne);
        department.update("这是删除测试2" + data.substring(10, 14), "this is delete2" + data.substring(10, 14)
                , "1", data.substring(10, 14), idOne);

        //通过从list接口返回的id,进行部门是删除
        department.delete(idOne).then().body("errcode", equalTo(0));
        department.delete(idTwo).then().body("errcode", equalTo(0));
    }

    @AfterClass
    public void tearDown() {
        //运行结束清理数据
        department.deleteAll();
    }
}

开始执行测试之前我们需要对结果验证,先注释掉@BfterClass,和@AfterClass中的deleteAll(),然后运行结果如下:
image.png
企业微信后台页面数据:
image.png
结果校验通过,开启@BfterCass,@AfterClass进行一轮测试,结果如下:
image.png
image.png

2.6引入json模板,当参数较多有些不会校验的字段我也需要测试,每跑一轮接口就造一轮显的不是很明智,这个时候需要我们引入json模板:

1).需要的pom依赖如下:

                 <!-- jsonPath依赖 -->
        <dependency>
            <groupId>com.jayway.jsonpath</groupId>
            <artifactId>json-path</artifactId>
            <version>2.4.0</version>
        </dependency>

2).在企业微信找一个参数较多的API,接口描述如下:
image.png
image.png
image.png
image.png
3).新建一个json文件,如下:
image.png
createUser的json模板:
create.json

4).new class命名user,测试能不能获取json模板中的数据,代码如下:

import cn.Knife.Wework.Utils.Rest_Perfect;
import com.jayway.jsonpath.JsonPath;
import io.restassured.response.Response;


/**
 * @author Knife
 * @description
 * @createTime 2020-05-08 21:33
 */
public class User extends Rest_Perfect {

    public Response create(String userid,String name,String mobile){
        String body = JsonPath.parse(User.class.getResourceAsStream("/user/create.json"))
                .set("$.userid", userid)
                .set("$.name", name)
                .set("$.mobile", mobile).jsonString();
        System.out.println(body);
        return null;
    }

}

5).json模板导入成功,我们来看看User下面其他API,一并把case写了:
通过Generate生成 case,代码如下:

/**
 * @author Knife
 * @description
 * @createTime 2020-05-08 21:46
 */
public class UserTest {

    private  User user = new User();

    @Test
    public void testCreate() {
        user.create("userid_test","name_test","183_test");
    }
}

从运行结果看,json模板替换数据成功:
image.png
6).准备yaml文件,写入数据,如下:
image.png
7).重构User类中的create方法,代码如下:

import cn.Knife.Wework.Utils.Rest_Perfect;
import com.jayway.jsonpath.JsonPath;
import io.restassured.response.Response;

import java.util.HashMap;


/**
 * @author Knife
 * @description
 * @createTime 2020-05-08 21:33
 */
public class User extends Rest_Perfect {


    /**
     * 创建user
     * @param userid    userid
     * @param name      名字
     * @param mobile    电话
     * @param email     邮箱
     * @return
     */
    public Response create(String userid,String name,String mobile,String email){
        String body = JsonPath.parse(User.class.getResourceAsStream("/user/create.json"))
                .set("$.userid", userid)
                .set("$.name", name)
                .set("$.mobile", mobile)
                .set("$.email", email)
                .jsonString();
        HashMap<String,Object> parame = new HashMap<String, Object>() {{
                put("$.body",body);
        }};
        return getResponseFromYaml("/user/create.yaml",parame);
    }

}

鼠标右击Generate生成case,代码如下:

import org.testng.annotations.Test;

import static org.hamcrest.Matchers.equalTo;


/**
 * @author Knife
 * @description
 * @createTime 2020-05-08 21:46
 */
public class UserTest {

    private User user = new User();

    @Test
    public void testCreate() {
        String data = user.getSystemDate();
        String userid = "Knife_" + data.substring(6, 12);
        String name = "Knife" + data.substring(10, 14);
        String mobile = "183" + data.substring(6, 14);
        String email = data.substring(6, 12) + "@163.com";
        user.create(userid, name, mobile,email).then().body("errcode", equalTo(0));
    }
}

运行结果如下:
image.png

image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
重写过的User类,代码如下:

import cn.Knife.Wework.Utils.Rest_Perfect;
import com.alibaba.fastjson.JSON;
import com.jayway.jsonpath.JsonPath;
import io.restassured.response.Response;

import java.util.HashMap;
import java.util.List;


/**
 * @author Knife
 * @description
 * @createTime 2020-05-08 21:33
 */
public class User extends Rest_Perfect {


    /**
     * 创建user
     * @param userid    userid
     * @param name      名字
     * @param mobile    电话
     * @param email     邮箱
     * @return
     */
    public Response create(String userid,String name,String mobile,String email){
        String body = JsonPath.parse(User.class.getResourceAsStream("/user/create.json"))
                .set("$.userid", userid)
                .set("$.name", name)
                .set("$.mobile", mobile)
                .set("$.email", email)
                .jsonString();
        HashMap<String,Object> parame = new HashMap<String, Object>() {{
                put("$.body",body);
        }};
        return getResponseFromYaml("/user/create.yaml",parame);
    }


    /**
     * 读取成员
     * @param userid
     * @return
     */
    public Response get(String userid) {
        HashMap<String,Object> parame = new HashMap<String, Object>() {{
            put("userid",userid);
        }};
        return getResponseFromYaml("/user/get.yaml",parame);
    }


    /**
     * 更新user
     * @param userid    userid
     * @param name      名字
     * @param mobile    电话
     * @param email     邮箱
     * @return
     */
    public Response update(String userid,String name,String mobile,String email) {
        String body = JsonPath.parse(User.class.getResourceAsStream("/user/create.json"))
                .set("$.userid", userid)
                .set("$.name", name)
                .set("$.mobile", mobile)
                .set("$.email", email)
                .jsonString();
        HashMap<String,Object> parame = new HashMap<String, Object>() {{
            put("$.body",body);
        }};
        return getResponseFromYaml("/user/update.yaml",parame);
    }


    /**
     * 删除user
     * @param userid
     * @return
     */
    public Response delete(String userid) {
        HashMap<String,Object> parame = new HashMap<String, Object>() {{
            put("userid",userid);
        }};
        return getResponseFromYaml("/user/delete.yaml",parame);
    }

    /**
     * 批量删除user
     * @param userIdList
     * @return
     */
    public Response batchdelete(List<String> userIdList) {
        HashMap<String,Object> parame = new HashMap<String, Object>() {{
            put("useridlist",userIdList);
        }};
        String body = JSON.toJSONString(parame);
        return getResponseFromYaml("/user/batchdelete.yaml",new HashMap<String, Object>(){{
            put("$.body",body);
        }});
    }

    /**
     * 获取部门成员详情
     * @param department_id    部门id
     * @param fetch_child       1/0:是否递归获取子部门下面的成员
     * @return
     */
    public Response simplelist(String department_id,String fetch_child) {
        HashMap<String,Object> parame = new HashMap<String, Object>() {{
            put("department_id",department_id);
            put("fetch_child",fetch_child);
        }};
        return getResponseFromYaml("/user/simplelist.yaml",parame);
    }

    /**
     * 获取部门成员详情
     * @param department_id    部门id
     * @param fetch_child       1/0:是否递归获取子部门下面的成员
     * @return
     */
    public Response list(String department_id,String fetch_child) {
        HashMap<String,Object> parame = new HashMap<String, Object>() {{
            put("department_id",department_id);
            put("fetch_child",fetch_child);
        }};
        return getResponseFromYaml("/user/list.yaml",parame);
    }

    /**
     * 删除全部的用户
     * @return
     */
    public Response deleteAll() {
        List<String> userIdList = list("1", "1").path("userlist.userid");
        //删除创始人数据
        userIdList.remove(0);
        userIdList.forEach(
                userId -> delete(userId)
        );
        return null;
    }
}

User类每个API对应的yaml文件:
batchdelete.yamlcreate.yamldelete.yamlget.yamllist.yamlsimplelist.yamlupdate.yaml

鼠标右击Generate生成case,如下:

import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

import java.util.Arrays;
import java.util.Objects;

import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;


/**
 * @author Knife
 * @description
 * @createTime 2020-05-08 21:46
 */
public class UserTest {

    private User user = new User();

    @BeforeClass
    public void setUp() {
        //如果user为空则创建,单例懒汉式
        user = Objects.nonNull(user) ? new User() : user;
        //运行前清理数据
        user.deleteAll();
    }

    @Test
    public void testCreate() {
        String data = user.getSystemDate();
        String userid = "Knife_" + data.substring(8, 14);
        String name = "Knife" + data.substring(10, 14);
        String mobile = "183" + data.substring(6, 14);
        String email = data.substring(8, 14) + "@163.com";
        user.create(userid, name, mobile, email).then().body("errcode", equalTo(0));
    }

    @Test
    public void testGet() {
        String data = user.getSystemDate();
        String userid = "Knife_" + data.substring(8, 14);
        String name = "Knife" + data.substring(10, 14);
        String mobile = "183" + data.substring(6, 14);
        String email = data.substring(8, 14) + "@163.com";
        //创建用户
        user.create(userid, name, mobile, email);
        //查询用户,断言name和email
        user.get(userid).then().body("errcode", equalTo(0))
                .body("name", equalTo(name))
                .body("email", equalTo(email));
        //未输入userid,判断errcode不等于0
        user.get("").then().body("errcode", not(equalTo(0)));
    }

    @Test
    public void testUpdate() {
        String data = user.getSystemDate();
        String userid = "Knife_" + data.substring(8, 14);
        String name = "Knife" + data.substring(10, 14);
        String mobile = "183" + data.substring(6, 14);
        String email = data.substring(8, 14) + "@163.com";
        user.create(userid, name, mobile, email).then().body("errcode", equalTo(0));
        //查询用户,断言name和email
        user.get(userid).then().body("errcode", equalTo(0))
                .body("name", equalTo(name))
                .body("email", equalTo(email));

        //更新用户name,email
        String updateName = "update" + data.substring(8, 14);
        String updateEmail = data.substring(8, 14) + "@Knife.com";
        user.update(userid, updateName, mobile, updateEmail).then().body("errcode", equalTo(0));
        //断言更新过的name,email
        user.get(userid).then().body("name", equalTo(updateName)).body("email", equalTo(updateEmail));
    }

    @Test
    public void testDelete() {
        String data = user.getSystemDate();
        String userid = "Knife_" + data.substring(8, 14);
        String name = "Knife" + data.substring(10, 14);
        String mobile = "183" + data.substring(6, 14);
        String email = data.substring(8, 14) + "@163.com";
        user.create(userid, name, mobile, email);

        //删除user
        user.delete(userid).then().body("errcode", equalTo(0));
    }


    @Test
    public void testBatchdelete() {
        String data = user.getSystemDate();
        String userid = "Knife_" + data.substring(8, 14);
        String name = "Knife" + data.substring(10, 14);
        String mobile = "183" + data.substring(6, 14);
        String email = data.substring(8, 14) + "@163.com";
        user.create(userid, name, mobile, email).then().body("errcode", equalTo(0));
        String userid2 = "Knife2" + data.substring(8, 14);
        user.update(userid, "two" + data.substring(8, 14), "132" + data.substring(6, 14), data.substring(6, 12) + "@Knife.com");

        user.batchdelete(Arrays.asList(userid, userid2)).then().body("errcode", equalTo(0));
        user.get(userid).then().body("errcode", not(equalTo(0)));
        user.get(userid2).then().body("errcode", not(equalTo(0)));
    }

    @Test
    public void testSimplelist() {
        user.simplelist("1", "1").then()
                .body("errcode", equalTo(0))
                .body("userlist[0].userid", equalTo("PengHui"));
    }

    @Test
    public void testList() {
        user.list("1", "1").then()
                .body("errcode", equalTo(0));
    }

    @AfterClass
    public void tearDown() {
        //运行后清理数据
        user.deleteAll();
    }
}

Rest_Info修改一条代码,没有实例化抛空指针:

    public HashMap<String, Object> query = new HashMap<>();

运行前,先注释掉deleteAll(),结果如下:
image.png
image.png
8).开启deleteAll()方法再次执行,结果如下:
image.png
image.png
9).执行顺利,转眼2.0篇已经到了结尾,最后我们再改造一次代码将json模板读取,拎出来:
1.Super_Utils中加入getTamplateFromJson()方法,代码如下:

     /**
     * 获取Json模板中的数据
     *
     * @param path   json文件路径
     * @param parame 模板中被替换的参数值
     * @return
     */
    public String getTamplateFromJson(String path, HashMap<String, Object> parame) {
        if (Objects.nonNull(path) && Objects.nonNull(parame)) {
            DocumentContext documentContext = JsonPath.parse(this.getClass().getResourceAsStream(path));
            parame.entrySet().forEach(
                    entry -> documentContext.set(entry.getKey(), entry.getValue())
            );
            return documentContext.jsonString();
        } else {
            throw new NullPointerException("ERR_PATH_OR_PARAME == NULL");
        }
    }

2.这里我展示调用的第一种用法,对user.create进行改造:

 /**
     * 创建user
     *
     * @param userid userid
     * @param name   名字
     * @param mobile 电话
     * @param email  邮箱
     * @return
     */
    public Response create(String userid, String name, String mobile, String email) {
        HashMap<String, Object> parame = new HashMap<String, Object>() {{
            put("userid", userid);
            put("name", name);
            put("mobile", mobile);
            put("email", email);
        }};
        //直接替换模板中的字段值,并返回模板数据
        String body = getTamplateFromJson("/user/create.json", parame);

        return getResponseFromYaml("/user/create.yaml", new HashMap<String, Object>() {{
            put("$.body", body);
        }});
    }

运行结果:
image.png
3.第二种写法,需要修改Super_Utils中的updateFromYamlRest_Info()方法,代码如下:

package cn.Knife.Wework.Utils;


import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import io.restassured.RestAssured;
import io.restassured.response.Response;
import io.restassured.specification.RequestSpecification;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Objects;

/**
 * @author Knife
 * @description
 * @createTime 2020-05-07 21:08
 */
public class Super_Utils {

    //储存token的容器
    public static String token[];

    //用于拼接path,此路径为resources根路径
    private static final String RESOURCES_PATH = "src/main/resources/";

    /**
     * 初始化RequestSpecification对象
     *
     * @return
     */
    public RequestSpecification getDefaultRequestSpecification() {
        return RestAssured.given();
    }


    /**
     * 获取Json模板中的数据
     *
     * @param path   json文件路径
     * @param parame 模板中被替换的参数值
     * @return
     */
    public String getTamplateFromJson(String path, HashMap<String, Object> parame) {
        if (Objects.nonNull(path) && Objects.nonNull(parame)) {
            DocumentContext documentContext = JsonPath.parse(this.getClass().getResourceAsStream(path));
            parame.entrySet().forEach(
                    entry -> documentContext.set(entry.getKey(), entry.getValue())
            );
            return documentContext.jsonString();
        } else {
            throw new NullPointerException("ERR_PATH_OR_PARAME == NULL");
        }
    }


    /**
     * 通过先更新yaml模板,获取rest_info对象,取出rest_info数据对接口发起请求,返回一个response
     * 传输规范:body:"$.body",header:"$.header",使用json模板,传path:"$.file"
     *
     * @param path   yaml路径从resources开始
     * @param parame 接口请求参数:"$.header",query,"$.body"
     * @return
     */
    public Response getResponseFromYaml(String path, HashMap<String, Object> parame) {
        Rest_Info rest_info = updateFromYamlRest_Info(path, parame);
        RequestSpecification requestSpecification;
        if (Objects.nonNull(rest_info)) {

            //调用getDefaultRequestSpecification()返回一个requestSpecification对象
            requestSpecification = getDefaultRequestSpecification();
            if (Objects.nonNull(rest_info.query)) {
                rest_info.query.entrySet().forEach(
                        entry -> requestSpecification.queryParam(entry.getKey(), entry.getValue())
                );
            }

            //给requestSpecification.body赋值这里采用三元表达式,如果rest_info中body不为空则赋值为rest_info.body,反则赋值为null
            requestSpecification.body(
                    ((Objects.nonNull(rest_info.body)) ? rest_info.body : "")
            );
            return requestSpecification
                    .when()
                    .request(rest_info.method, rest_info.url + rest_info.uri)
                    .then()
                    .extract().response();
        } else {
            throw new NullPointerException("ERR_REST_INFO == NULL!");
        }
    }

    /**
     * 通过parame更新Rest_Info中的数据
     *
     * @param path
     * @param parame
     * @return
     */
    public Rest_Info updateFromYamlRest_Info(String path, HashMap<String, Object> parame) {
        ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
        Rest_Info ri = Rest_Info.getInstance();
        if (Objects.nonNull(path) && Objects.nonNull(parame)) {
            Rest_Info rest_info = (Rest_Info) readYamlValue(objectMapper, path, ri);

            //将yaml中的method取出,转换为小写,进行比较
            if ("get".equals(rest_info.method.toLowerCase())) {

                //如果method为get,将parame中的数据添加到Rest_Info的query
                parame.entrySet().forEach(
                        entry -> rest_info.query.put(entry.getKey(), entry.getValue())
                );
            }

            //如果method为post,将parame中body取出,设置为Rest_Info的body。
            //这里的"$.body","$.file"只是parame中的key,就像一种规范,用一个标识符号代表某个数据
            if ("post".equals(rest_info.method.toLowerCase())) {

                //如果body和file都不为空,那就会造成参数错乱,按照执行循序给rest_info.body赋值
                //res_info.body的值每次进行赋值操作都会更新,如果接口增加参数也能通过json模板追加
                if ((Objects.nonNull(parame.get("$.body")) && (Objects.nonNull(parame.get("$.file"))))) {

                    //如果两种都要兼容,参考这里写法
                   /* Map<String, Object> bodyByMap =  JSONObject.parseObject(parame.get("$.body"));
                    String path = parame.get("$.file").toString();
                    parame.remove("$.file");
                    parame.remove("$.body");
                    Map<String,Object> b = JSONObject.parseObject(getTamplateFromJson(path, parame));
                    b.entrySet().forEach(
                            e -> bodyByMap.put(e.getKey(),e.getValue())
                    );
                    rest_info.body = JSON.toJSONString(bodyByMap);*/

                    throw new RuntimeException("请选择body,或json模板方式传参");
                }

                /*if (Objects.nonNull(parame.get("$.body"))) {
                    rest_info.body = parame.get("$.body").toString();
                }*/

                //如果body不为空则设置为rest_info.body
                rest_info.body = Objects.nonNull(parame.get("$.body")) ? parame.get("$.body").toString() : "";

                //如果file不为空,则使用json模板
                if (Objects.nonNull(parame.get("$.file"))) {
                    String jsonTamplatePth = parame.get("$.file").toString();
                    parame.remove("$.file");
                    rest_info.body = getTamplateFromJson(jsonTamplatePth, parame);
                }
            }

            //返回的rest_info对象,这里已经替换过query或body
            return rest_info;
        } else {
            throw new NullPointerException("ERR_PATH_OR_PARAME == NULL!");
        }
    }


    /**
     * @param path yaml文件路径
     * @param type 0:代表写入,1:代表读取
     * @param obj  接收或写入的对象
     * @return
     */
    public static Object YamlFactoryIsReadYamlAndWriterYaml(String path, String type, Object obj) {
        ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
        if (Objects.nonNull(type)) {
            try {
                if ("0".equals(type)) {
                    writerYaml(objectMapper, path, obj);
                }
                if ("1".equals(type)) {
                    //返回读取的yaml数据,序列化成对象
                    return readYamlValue(objectMapper, path, obj);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * 读取yaml文件数据
     *
     * @param objectMapper
     * @param path         resources下文件路径
     * @param obj          读取yaml数据的对象实体
     * @return
     */
    public static Object readYamlValue(ObjectMapper objectMapper, String path, Object obj) {
        if (Objects.nonNull(path)) {
            try {
                return objectMapper.readValue(Super_Utils.class.getResourceAsStream(path), obj.getClass());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * 传一个对象,写入内容到yaml
     *
     * @param objectMapper
     * @param path         resources下文件路径
     * @param obj          写入内容的对象
     */
    public static void writerYaml(ObjectMapper objectMapper, String path, Object obj) {
        if (Objects.nonNull(path) && Objects.nonNull(obj)) {
            //定义resource的路径,避免写的时候写入到target/classes下面
            path = RESOURCES_PATH + path;
            //获取一个输出流对象,指定写入文件路径
            try (OutputStream os = new FileOutputStream(new File(path))) {
                //写入对象到yaml文件
                objectMapper.writeValue(os, obj);
                //打印写入yaml的数据
                System.out.println("写入内容如下:");
                System.out.println("***********************************************************************");
                System.out.println(objectMapper.writeValueAsString(obj));
                System.out.println("***********************************************************************");
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            System.out.println(Objects.nonNull(path) ?
                    "当前写入yaml路径:" + path : "当前yaml路径为空!");
            System.out.println(Objects.nonNull(obj) ?
                    "当前写入内容:" + obj.toString() : "请检查传的obj,或当前写入内容为空!");
            throw new RuntimeException("ERR_PATH_OR_OBJ == NULL");
        }
    }
}

使用”$.file”方法,修改User.create()方法代码如下:

 /**
     * 创建user
     *
     * @param userid userid
     * @param name   名字
     * @param mobile 电话
     * @param email  邮箱
     * @return
     */
    public Response create(String userid, String name, String mobile, String email) {
        HashMap<String, Object> parame = new HashMap<String, Object>() {{
            put("userid", userid);
            put("name", name);
            put("mobile", mobile);
            put("email", email);
        }};
        parame.put("$.file", "/user/create.json");
        return getResponseFromYaml("/user/create.yaml", parame);
    }

执行结果如下:

image.png

2.0正式完结,将开始3.0引入report,allure2!!!

3.allure2测试报告引入:

注:allure官方文档:https://docs.qameta.io/allure/
1).通过scoop下载allure,按住Win+r,输入命令:

powershell //进入管理员命令行界面

2).在powershell输入命令安装scoop:

Set-ExecutionPolicy RemoteSigned -scope CurrentUser
iwr -useb get.scoop.sh | iex

安装成功,输入soccp出现帮助文档,安装成功:
image.png
输入命令,安装alure:

scoop install allure

安装成功,输入allure可以看见帮助文档,如下:
image.png
3).testNG生成allure测试报告的pom依赖:

     <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>${maven.compiler.source}</maven.compiler.target>
        <aspectj.version>1.8.10</aspectj.version>
        <maven.build.timestamp.format>yyyyMMddHHmmss</maven.build.timestamp.format>
    </properties>

        <dependencies>

        <!-- https://mvnrepository.com/artifact/io.qameta.allure/allure-testng -->
        <dependency>
            <groupId>io.qameta.allure</groupId>
            <artifactId>allure-testng</artifactId>
            <version>2.13.3</version>
        </dependency>
    </dependencies>

在通过mvn test执行测试用例的时候,springboot打包失败了,先注释掉springboot的pom依赖:

<!--注释springboot -->
<!-- <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <version>2.2.6.RELEASE</version>
    </parent>-->

image.png
4)在idea底部标签栏点开Terminal,输入命令:mvn test,扫码执行测试用例,结果如下:
ps:这里有一条用例断言失败,我们先忽略!
image.png
执行完毕之后target目录下会出现surefire-reports
image.png
5).拷贝surefire-reports的路径:
image.png
6).在idea点开Terminal,输入命令:allure serve target/surefire-reports,启动服务器,打开allure测试报告:
image.png
allure测试报告如下:
image.png
image.png
用例执行失败,这里也看的比较清楚,断言错误。这是之前我们演示错误获取token:
image.png
7).在pom中我们指定一下allure测试报告的输出路径,不通过mvn命令,我们也能输出测试报告了:

<build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.20</version>
                <configuration>
                    <systemPropertyVariables>
                        <allure.results.directory>${project.build.directory}/allure-results/${maven.build.timestamp}
                        </allure.results.directory>
                        <allure.link.issue.pattern>https://example.org/browse/{}</allure.link.issue.pattern>
                        <allure.link.tms.pattern>https://example.org/browse/{}</allure.link.tms.pattern>
                    </systemPropertyVariables>
                    <argLine>
                        -javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
                    </argLine>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>org.aspectj</groupId>
                        <artifactId>aspectjweaver</artifactId>
                        <version>${aspectj.version}</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>

新建一个xml,通过xml管理测试集:

<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
<suite name="Knife_Rest-Assured" thread-count="3" verbose="1">
    <test name="部门管理测试">
        <classes>
            <class name="cn.Knife.Wework.DepartmentTest"></class>
        </classes>
    </test>
    <test name="增删除查用户测试">
        <classes>
        <class name="cn.Knife.Wework.AddressBookManagement.UserTest"></class>
    </classes>
    </test>
</suite>

执行结果:
image.png
此时target目录下已经生成了allure-report,自动按照日期给每天执行的用例分文件夹:
image.png
输入命令:allure serve target/allure-results/20200509083719,会自动打开浏览器,显示测试报告
image.png

3.1allure注解的使用:

1).修改后的departmentTest代码如下:

package cn.Knife.Wework;

import cn.Knife.Wework.AddressBookManagement.Department;
import io.qameta.allure.*;
import org.testng.annotations.*;

import java.util.Objects;

import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItems;

/**
 * @author Knife
 * @description
 * @createTime 2020-05-07 17:52
 */
@Epic("企业微信-部门管理")   //模块
public class DepartmentTest {

    private static Department department;

    @Description("运行前初始化,运行前清理数据")  //用例描述
    @Step("初始化Department对象")    //执行步骤
    @BeforeClass
    public void setUp() {
        department = Objects.isNull(department) ? new Department() : department;
        //初始化时清理测试数据
        department.deleteAll();
    }


    @TmsLink("department__create_01")    //用例编号
    @Issue("0101")  //bug编号
    @Description("验证部门创建正确业务流程")  //描述
    @Story("部门创建")  //执行步骤
    @Test
    public void testCreate() {
        String data = department.getSystemDate();
        String name = "高级测试部门" + data;
        String nameTwo = "高级测试部门1" + data;
        department.create(name, "SUPER_Test" + data, "1", "1", data)
                .then().statusCode(200).body("errcode", equalTo(0));

        department.create(nameTwo, "SUPER_Test2" + data, "1", "1", data.substring(8, 14))
                .then().statusCode(200).body("errcode", equalTo(0));

        //通过list接口查询到id
        String idOne = department.list("").path("department.find { it.name == '" + name + "'}.id").toString();
        String idTwo = department.list("").path("department.find { it.name == '" + nameTwo + "'}.id").toString();

        //修改创建的部门名称和别名
        department.update("测试进行中" + data.substring(10, 14), "update" + data.substring(10, 14)
                , "1", data.substring(10, 14), idOne);
        department.update("测试进行中" + data.substring(10, 14), "update" + data.substring(10, 14)
                , "1", data.substring(10, 14), idOne);
    }


    @Description("验证修改部门的正确业务流程")
    @Story("修改部门")
    @Test
    public void testUpdate() {
        String data = department.getSystemDate();
        String name = "这是修改试部门测试" + data;
        String nameTwo = "这是修改试部门测试1" + data;
        department.create(name, "Update_Test" + data, "1", "1", data);

        department.create(nameTwo, "Update_Test2" + data, "1", "1", data.substring(8, 14));

        //通过list接口查询到id
        String idOne = department.list("").path("department.find { it.name == '" + name + "'}.id").toString();
        String idTwo = department.list("").path("department.find { it.name == '" + nameTwo + "'}.id").toString();

        //修改创建的部门名称和别名
        department.update("这是修改测试" + data.substring(10, 14), "this is update" + data.substring(10, 14)
                , "1", data.substring(10, 14), idOne);
        department.update("这是修改测试2" + data.substring(10, 14), "this is update2" + data.substring(10, 14)
                , "1", data.substring(10, 14), idOne);
    }

    //@Test(description = "获取部门列表")
    public void testList() {

        //我知道你们在想有没有断言多个参数,rest-assured它支持多个参数断言,同时也能断言多个字段
        department.list("").then().statusCode(200)
                //因为我刚开始已经创建一个了,所以这里断言三个部门
                .body("department.name", hasItems("Knife", "更新高级测试部门", "高级测试部门1"))
                .body("errcode", equalTo(0));

        department.list("1").then()
                .body("department.id", hasItems(1, 100, 101))
                .body("errcode", equalTo(0));

        department.list("").then().statusCode(200)
                //引用官方原话:注意这里的"json path"语法使用的是Groovy的GPath标注法,不要和Jayway的JsonPath语法混淆
                .body("department.find { it.id==100 }.name", equalTo("更新高级测试部门"));
    }

    @Description("删除部门正确业务场景测试")
    @Step("删除部门")
    @Test
    public void testDelete() {
        String data = department.getSystemDate();
        String name = "删除部门测试" + data;
        String nameTwo = "删除部门测试1" + data;
        department.create(name, "delete_Test" + data, "1", "1", data);

        department.create(nameTwo, "delete_Test2" + data, "1", "1", data.substring(8, 14));

        //通过list接口查询到id
        String idOne = department.list("").path("department.find { it.name == '" + name + "'}.id").toString();
        String idTwo = department.list("").path("department.find { it.name == '" + nameTwo + "'}.id").toString();

        //修改创建的部门名称和别名
        department.update("这是删除测试" + data.substring(10, 14), "this is delete" + data.substring(10, 14)
                , "1", data.substring(10, 14), idOne);
        department.update("这是删除测试2" + data.substring(10, 14), "this is delete2" + data.substring(10, 14)
                , "1", data.substring(10, 14), idOne);

        //通过从list接口返回的id,进行部门是删除
        department.delete(idOne).then().body("errcode", equalTo(0));
        department.delete(idTwo).then().body("errcode", equalTo(0));
    }

    @AfterClass(description = "运行后,清理数据")
    @Story("运行后清理数据,创建一个部门")
    public void tearDown() {
        //运行结束清理数据
        department.deleteAll();
        department.create("name", "SUPER_Test" + "data", "1", "1", "2")
                .then().statusCode(200).body("errcode", equalTo(0));
    }
}

修改后的UserTest类:

package cn.Knife.Wework.AddressBookManagement;

import io.qameta.allure.Description;
import io.qameta.allure.Epic;
import io.qameta.allure.Story;
import io.qameta.allure.TmsLink;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

import java.util.Arrays;
import java.util.Objects;

import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;


/**
 * @author Knife
 * @description
 * @createTime 2020-05-08 21:46
 */
@Epic("企业微信-用户管理")
public class UserTest {

    private static User user;


    @Description("运行前,初始化user对象,清理测试数据")
    @Story("初始化User对象")
    @BeforeClass
    public void setUp() {
        //如果user为空则创建,单例懒汉式
        user = Objects.isNull(user) ? new User() : user;
        //运行前清理数据
        user.deleteAll();
    }


    @Description("创建用户正确业务流程")
    @TmsLink("case_createUser_1")
    @Story("创建用户")
    @Test
    public void testCreate() {
        String data = user.getSystemDate();
        String userid = "Knife_" + data.substring(8, 14);
        String name = "Knife" + data.substring(10, 14);
        String mobile = "183" + data.substring(6, 14);
        String email = data.substring(8, 14) + "@163.com";
        user.create(userid, name, mobile, email).then().body("errcode", equalTo(0));
    }


    @Description("查询用户正确业务流程")
    @TmsLink("case_getUser_2")
    @Story("查询用户")
    @Test
    public void testGet() {
        String data = user.getSystemDate();
        String userid = "Knife_" + data.substring(8, 14);
        String name = "Knife" + data.substring(10, 14);
        String mobile = "183" + data.substring(6, 14);
        String email = data.substring(8, 14) + "@163.com";
        //创建用户
        user.create(userid, name, mobile, email);
        //查询用户,断言name和email
        user.get(userid).then().body("errcode", equalTo(0))
                .body("name", equalTo(name))
                .body("email", equalTo(email));
        //未输入userid,判断errcode不等于0
        user.get("").then().body("errcode", not(equalTo(0)));
    }


    @Description("修改用户正确业务流程")
    @TmsLink("case_updateUser_3")
    @Story("更新用户资料")
    @Test
    public void testUpdate() {
        String data = user.getSystemDate();
        String userid = "Knife_" + data.substring(8, 14);
        String name = "Knife" + data.substring(10, 14);
        String mobile = "183" + data.substring(6, 14);
        String email = data.substring(8, 14) + "@163.com";
        user.create(userid, name, mobile, email).then().body("errcode", equalTo(0));
        //查询用户,断言name和email
        user.get(userid).then().body("errcode", equalTo(0))
                .body("name", equalTo(name))
                .body("email", equalTo(email));

        //更新用户name,email
        String updateName = "update" + data.substring(8, 14);
        String updateEmail = data.substring(8, 14) + "@Knife.com";
        user.update(userid, updateName, mobile, updateEmail).then().body("errcode", equalTo(0));
        //断言更新过的name,email
        user.get(userid).then().body("name", equalTo(updateName)).body("email", equalTo(updateEmail));
    }


    @Description("删除用户正确业务流程")
    @TmsLink("case_deleteUser_4")
    @Story("删除用户")
    @Test
    public void testDelete() {
        String data = user.getSystemDate();
        String userid = "Knife_" + data.substring(8, 14);
        String name = "Knife" + data.substring(10, 14);
        String mobile = "183" + data.substring(6, 14);
        String email = data.substring(8, 14) + "@163.com";
        user.create(userid, name, mobile, email);

        //删除user
        user.delete(userid).then().body("errcode", equalTo(0));
    }


    @Description("批量删除用户正确业务流程")
    @TmsLink("case_batchDeleteUser_5")
    @Story("批量删除用户")
    @Test
    public void testBatchdelete() {
        String data = user.getSystemDate();
        String userid = "Knife_" + data.substring(8, 14);
        String name = "Knife" + data.substring(10, 14);
        String mobile = "183" + data.substring(6, 14);
        String email = data.substring(8, 14) + "@163.com";
        user.create(userid, name, mobile, email).then().body("errcode", equalTo(0));
        String userid2 = "Knife2" + data.substring(8, 14);
        user.update(userid, "two" + data.substring(8, 14), "132" + data.substring(6, 14), data.substring(6, 12) + "@Knife.com");

        user.batchdelete(Arrays.asList(userid, userid)).then().body("errcode", equalTo(0));
        user.get(userid).then().body("errcode", not(equalTo(0)));
        user.get(userid2).then().body("errcode", not(equalTo(0)));
    }


    @Description("获取部门详情正确业务流程")
    @TmsLink("case_simpleListUser_6")
    @Story("获取部门成员详情")
    @Test
    public void testSimplelist() {
        user.simplelist("1", "1").then()
                .body("errcode", equalTo(0))
                .body("userlist[0].userid", equalTo("PengHui"));

        user.simplelist("1", "0").then()
                .body("errcode", equalTo(0));
    }


    @Description("获取部门成员正确/错误业务流程")
    @TmsLink("case_listUser_7")
    @Story("获取部门成员详情")
    @Test
    public void testList() {
        user.list("1", "1").then()
                .body("errcode", equalTo(0));

        user.list("0", "0").then()
                .body("errcode", not(equalTo(0)));
    }


    @Description("运行后清理测试数据")
    @Story("数据清理")
    @AfterClass
    public void tearDown() {
        //运行后清理数据
        user.deleteAll();
    }
}

allure报告显示:模块,执行步骤显示,跑case花费的总时间,每条case执行时间,执行顺序
image.png
allure-report报告展示,下载后用编译器打开:
allure-report.7z
image.png
可以查看我之前跑的case,以及注解的作用
image.png

4.记录执行步骤,Log4j框架引入、使用,添加pom依赖:

        <!-- log4j依赖 -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

1).在resources目录下新建log4j.properties文件,写入日志配置:

###根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/Knife_Rest-Assured.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

2).先说下log4j的几种log级别的等级:
日志记录器(Logger)的行为是分等级的。如下表所示:
分 为OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL或者您定义的级别,这些级别是从高到低的级别。Log4j建议只使用四个级别,优先级从高到低分别是 ERROR、WARN、INFO、DEBUG。通过在这里定义的级别,您可以控制到应用程序中相应级别的日志信息的开关。比如在这里定义了INFO级别, 则应用程序中所有DEBUG级别的日志信息将不被打印出来;
3).使用示例,我在Super_Utils中添加了日志,如下:

package cn.Knife.Wework.Utils;


import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import io.restassured.RestAssured;
import io.restassured.response.Response;
import io.restassured.specification.RequestSpecification;
import org.apache.log4j.Logger;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Objects;

/**
 * @author Knife
 * @description
 * @createTime 2020-05-07 21:08
 */
public class Super_Utils {

    //初始化日志加载器
    private static Logger logger = Logger.getLogger(Super_Utils.class);

    //储存token的容器
    public static String token[];

    //用于拼接path,此路径为resources根路径
    private static final String RESOURCES_PATH = "src/main/resources/";


    /**
     * 初始化RequestSpecification对象
     *
     * @return
     */
    public RequestSpecification getDefaultRequestSpecification() {

        logger.info("               ____  __.        .__   _____                        \n" +
                "  /\\|\\/\\       |    |/ _|  ____  |__|_/ ____\\  ____        /\\|\\/\\   \n" +
                " _)    (__     |      <   /    \\ |  |\\   __\\ _/ __ \\      _)    (__ \n" +
                " \\_     _/     |    |  \\ |   |  \\|  | |  |   \\  ___/      \\_     _/ \n" +
                "   )    \\   /\\ |____|__ \\|___|  /|__| |__|    \\___  > /\\    )    \\  \n" +
                "   \\/\\|\\/   \\/         \\/     \\/                  \\/  \\/    \\/\\|\\/  ");

        return RestAssured.given();
    }


    /**
     * 获取Json模板中的数据
     *
     * @param path   json文件路径
     * @param parame 模板中被替换的参数值
     * @return
     */
    public String getTamplateFromJson(String path, HashMap<String, Object> parame) {
        if (Objects.nonNull(path) && Objects.nonNull(parame)) {
            DocumentContext documentContext = JsonPath.parse(this.getClass().getResourceAsStream(path));
            parame.entrySet().forEach(
                    entry -> documentContext.set(entry.getKey(), entry.getValue())
            );
            return documentContext.jsonString();
        }
        logger.warn("当前未获取Json模板中的数据!");
        logger.warn("当前读取模板路径:" + (Objects.nonNull(path) ? path: "NULL"));
        logger.warn("替换模板内容:" + (Objects.nonNull(parame) ? parame: "NULL"));
        return null;
    }


    /**
     * 通过先更新yaml模板,获取rest_info对象,取出rest_info数据对接口发起请求,返回一个response
     * 传输规范:body:"$.body",header:"$.header",使用json模板,传path:"$.file"
     *
     * @param path   yaml路径从resources开始
     * @param parame 接口请求参数:"$.header",query,"$.body"
     * @return
     */
    public Response getResponseFromYaml(String path, HashMap<String, Object> parame) {
        Rest_Info rest_info = updateFromYamlRest_Info(path, parame);
        RequestSpecification requestSpecification;
        if (Objects.nonNull(rest_info)) {

            //调用getDefaultRequestSpecification()返回一个requestSpecification对象
            requestSpecification = getDefaultRequestSpecification();
            if (Objects.nonNull(rest_info.query)) {
                rest_info.query.entrySet().forEach(
                        entry -> requestSpecification.queryParam(entry.getKey(), entry.getValue())
                );
            }

            //给requestSpecification.body赋值这里采用三元表达式,如果rest_info中body不为空则赋值为rest_info.body,反则赋值为null
            requestSpecification.body(
                    ((Objects.nonNull(rest_info.body)) ? rest_info.body : "")
            );
            return requestSpecification
                    .when()
                    .request(rest_info.method, rest_info.url + rest_info.uri)
                    .then()
                    .extract().response();
        }
        logger.warn("当前返回对象为NULL,rest_info:" + rest_info);
        return null;
    }

    /**
     * 通过parame更新Rest_Info中的数据
     *
     * @param path
     * @param parame
     * @return
     */
    public Rest_Info updateFromYamlRest_Info(String path, HashMap<String, Object> parame) {
        ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
        Rest_Info ri = Rest_Info.getInstance();
        if (Objects.nonNull(path) && Objects.nonNull(parame)) {
            Rest_Info rest_info = (Rest_Info) readYamlValue(objectMapper, path, ri);

            //将yaml中的method取出,转换为小写,进行比较
            if ("get".equals(rest_info.method.toLowerCase())) {

                //如果method为get,将parame中的数据添加到Rest_Info的query
                parame.entrySet().forEach(
                        entry -> {
                            rest_info.query.put(entry.getKey(), entry.getValue());
                        });
            }

            //如果method为post,将parame中body取出,设置为Rest_Info的body。
            //这里的"$.body","$.file"只是parame中的key,就像一种规范,用一个标识符号代表某个数据
            if ("post".equals(rest_info.method.toLowerCase())) {

                //如果body和file都不为空,那就会造成参数错乱,按照执行循序给rest_info.body赋值
                //res_info.body的值每次进行赋值操作都会更新,如果接口增加参数也能通过json模板追加
                if ((Objects.nonNull(parame.get("$.body")) && (Objects.nonNull(parame.get("$.file"))))) {

                    //如果两种都要兼容,参考这里写法
                   /* Map<String, Object> bodyByMap =  JSONObject.parseObject(parame.get("$.body"));
                    String path = parame.get("$.file").toString();
                    parame.remove("$.file");
                    parame.remove("$.body");
                    Map<String,Object> b = JSONObject.parseObject(getTamplateFromJson(path, parame));
                    b.entrySet().forEach(
                            e -> bodyByMap.put(e.getKey(),e.getValue())
                    );
                    rest_info.body = JSON.toJSONString(bodyByMap);*/
                    logger.warn("请选择body,或json模板方式传参");
                    return null;
                }

                //如果body不为空则设置为rest_info.body
                if (Objects.nonNull(parame.get("$.body"))) {
                    rest_info.body = parame.get("$.body").toString();

                    logger.info("当前参数获取方式:从parame中取出body");
                }
                //如果file不为空,则使用json模板
                if (Objects.nonNull(parame.get("$.file"))) {
                    String jsonTamplatePth = parame.get("$.file").toString();
                    parame.remove("$.file");
                    rest_info.body = getTamplateFromJson(jsonTamplatePth, parame);

                    logger.info("当前参数获取方式:Json模板");
                }
            }

            //返回的rest_info对象,这里已经替换过query或body
            return rest_info;
        }
        logger.warn("Path:" + (Objects.nonNull(path) ? path : "Null"));
        logger.warn("Parame:" + (Objects.nonNull(parame) ? JSON.toJSONString(parame) : "Null"));
        return null;
    }


    /**
     * @param path yaml文件路径
     * @param type 0:代表写入,1:代表读取
     * @param obj  接收或写入的对象
     * @return
     */
    public static Object YamlFactoryIsReadYamlAndWriterYaml(String path, String type, Object obj) {
        ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
        if (Objects.nonNull(type)) {
            logger.info("当前操作状态:" + (type.equals("0") ? "写入" : "读取"));
            if ("0".equals(type)) {
                writerYaml(objectMapper, path, obj);
            }
            if ("1".equals(type)) {
                //返回读取的yaml数据,序列化成对象
                return readYamlValue(objectMapper, path, obj);
            }
        }
        logger.warn("当前Type为空,请传Type值");
        return null;
    }

    /**
     * 读取yaml文件数据
     *
     * @param objectMapper
     * @param path         resources下文件路径
     * @param obj          读取yaml数据的对象实体
     * @return
     */
    public static Object readYamlValue(ObjectMapper objectMapper, String path, Object obj) {
        if (Objects.nonNull(path)) {
            try {
                return objectMapper.readValue(Super_Utils.class.getResourceAsStream(path), obj.getClass());
            } catch (IOException e) {
                logger.error("文件读取异常,请检查当前读取路径:" + path);
                logger.error("报错内容:" + e);
            }
        }
        logger.warn("当前读取Yaml路径为空");
        return null;
    }

    /**
     * 传一个对象,写入内容到yaml
     *
     * @param objectMapper
     * @param path         resources下文件路径
     * @param obj          写入内容的对象
     */
    public static void writerYaml(ObjectMapper objectMapper, String path, Object obj) {
        if (Objects.nonNull(path) && Objects.nonNull(obj)) {
            //定义resource的路径,避免写的时候写入到target/classes下面
            path = RESOURCES_PATH + path;
            //获取一个输出流对象,指定写入文件路径
            try (OutputStream os = new FileOutputStream(new File(path))) {
                //写入对象到yaml文件
                objectMapper.writeValue(os, obj);
                //打印写入yaml的数据
                logger.info("写入内容如下:");
                logger.info("***********************************************************************");
                logger.info(objectMapper.writeValueAsString(obj));
                logger.info("***********************************************************************");
            } catch (IOException e) {
                logger.error("文件读写异常,请检查路径:" + path);
                logger.info("当前写入内容:" + obj.toString());
                logger.error("报错内容:" + e);
            }
        }
    }
}

执行时console打印:
image.png
输出到文件,记录重要执行步骤,错过执行,出现bug也能迅速定位:
image.png

5.工具加装,增加一个mysql查询类,进行数据库结果集校验:

1).新建一个properties文件,将连接信息配置化,如下:
注:properties文件存储内容的格式是key:value,取也一样!

###jdbc.name###
jdbc.driver = com.mysql.jdbc.Driver
###连接地址###
jdbc.url = jdbc:mysql://localhost:3306/test?serverTimezone=UTC
###用户名###
jdbc.username = root
###密码###
jdbc.password = p2538699146

2).在Utils包下新建一个类,取名JdbcQueryUtil,先写一个解析properties配置文件的方法,如下:

package cn.Knife.Wework.Utils;

import org.apache.log4j.Logger;

import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Objects;
import java.util.Properties;

/**
 * @author Knife
 * @description
 * @createTime 2020-05-11 0:21
 */
public class JdbcQuery {

    //添加当前对象到日志容器
    private static Logger logger = Logger.getLogger(JdbcQuery.class);

    //properties工具
    private static Properties properties;

    //jdbc配置文件路径
    private static final String JDBC_PATH = "/conf/jdbc.properties";   

    //获取数据库连接对象
    private static Connection connection;

    /**
     * 初始化,加载properties
     *
     * @return
     */
    public static Properties getProperties() {
        //如果properties为null,则创建
        if (Objects.isNull(properties)) {
            properties = new Properties();
        }

          //通过内部代码块加载properties配置内容
        {
            try {
                properties.load(JdbcQueryUtil.class.getResourceAsStream(JDBC_PATH));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return properties;
    }
}

鼠标右击Generate生成case,测试我们的代码能不能获取到properties文件的内容,代码如下:

package cn.Knife.Wework.Utils;

import org.testng.annotations.Test;

import java.util.Properties;

import static org.testng.Assert.*;

/**
 * @author Knife
 * @description
 * @createTime 2020-05-11 1:41
 */
public class JdbcQueryUtilTest {

    @Test
    public void testGetProperties() {
        JdbcQueryUtil jdbcQueryUtil = new JdbcQueryUtil();
        //获取一个properties对象
        Properties properties = jdbcQueryUtil.getProperties();
        System.out.println(properties.getProperty("jdbc.url"));
        System.out.println(properties.getProperty("jdbc.driver"));
        System.out.println(properties.getProperty("jdbc.username"));
        System.out.println(properties.getProperty("jdbc.password"));
    }
}

执行结果:
image.png
3).获取到properties文件配置信息,开始编写操作数据库的方法:
1.添加pom依赖:


        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>6.0.6</version>
        </dependency>

2.在JdbcQueryUtil类中,添加两个方法,1.获取数据库连接,2.执行sql语句的util。代码如下:

package cn.Knife.Wework.Utils;

import com.alibaba.fastjson.JSON;
import org.apache.log4j.Logger;

import java.io.IOException;
import java.sql.*;
import java.util.HashMap;
import java.util.Objects;
import java.util.Properties;

/**
 * @author Knife
 * @description
 * @createTime 2020-05-11 0:21
 */
public class JdbcQueryUtil {

    //properties工具
    private static Properties properties;

    //jdbc配置文件路径
    private static final String JDBC_PATH = "/conf/jdbc.properties";

    //添加当前对象到日志容器
    private static Logger logger = Logger.getLogger(JdbcQueryUtil.class);

    //获取数据库连接对象
    private static Connection connection;

    private static JdbcQueryUtil jdbcQueryUtil;


    /**
     * 获取jdbcQueryUtil对象
     *
     * @return
     */
    public static JdbcQueryUtil getInstance() {
        if (Objects.isNull(jdbcQueryUtil)) {
            jdbcQueryUtil = new JdbcQueryUtil();
        }
        return jdbcQueryUtil;
    }

    /**
     * 初始化,加载properties
     *
     * @return
     */
    public static Properties getProperties() {
        //如果properties为null,则创建
        if (Objects.isNull(properties)) {
            properties = new Properties();
        }

        //通过内部代码块加载properties配置内容
        {
            if (Objects.nonNull(properties)) {
                try {
                    properties.load(JdbcQueryUtil.class.getResourceAsStream(JDBC_PATH));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return properties;
    }

    /**
     * 修改或查询数据库
     * map中key的表示:修改:"$.update",查询:"$.query"
     * @param sql
     * @return  返回每个字段查询到的结果集
     */
    public static HashMap<String, String> jdbcQueryAndUpdate(HashMap<String, String> sql) {
        //定义储存查询结果的容器
        HashMap<String, String> queryValues = new HashMap<>();

        //获取数据库连接
        connection = getConnection();

        //sql不为空,开始提供操作
        if (Objects.nonNull(sql)) {

            //定义PreparedStatement对象,此对象有操作数据库的方法
            PreparedStatement preparedStatement;
            try {
                //判断当前是更新还是查询数据库
                if (Objects.nonNull(sql.get("$.update"))) {

                    logger.info("开始执行更新数据操作!");

                    //如果是更新执行更新操作
                    preparedStatement = connection.prepareStatement(sql.get("$.update"));
                    //执行修改操作
                    preparedStatement.executeUpdate();

                    logger.info("更新成功,执行的sql:" + sql.get("$.update"));
                }

                if (Objects.nonNull(sql.get("$.query"))) {

                    logger.info("开始执行查询操作!");

                    preparedStatement = connection.prepareStatement(sql.get("$.query"));

                    //执行查询,获得结果集
                    ResultSet resultSet = preparedStatement.executeQuery();

                    //获取查询的相关信息
                    ResultSetMetaData metaData = resultSet.getMetaData();

                    //获取查询字段的数值
                    int columnCount = metaData.getColumnCount();

                    while (resultSet.next()) {
                        //循环取出每个字段的值
                        for (int i = 0; i < columnCount; i++) {

                            //获取查询字段
                            String queryField = metaData.getColumnLabel(i);
                            String queryValue = null;
                            //判断当前字段是否为空
                            if (Objects.nonNull(queryField)) {
                                //如果不为空,取出查询到的value
                                queryValue = resultSet.getObject(queryField).toString();
                            }

                            //将查询到的结果添加到map
                            queryValues.put(queryField,(Objects.nonNull(queryValue) ? queryValue :""));

                            logger.info("当前执行查询sql:" + sql.get("$.query"));
                            logger.info("查询字段:" + queryField);
                            logger.info("查询结果:" + queryValue);
                        }
                    }
                    //返回查询到的结果集
                    return queryValues;
                }
            } catch (SQLException e) {
                logger.error("出现报错,请检查sql集:" + JSON.toJSONString(sql));
                logger.error("报错内容:" + e);
            } finally {

                logger.info("开始关闭数据库连接");

                if (connection != null) {
                    try {
                        connection.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return null;
    }

    /**
     * 获取数据库连接
     *
     * @return
     */
    private static Connection getConnection() {
        if (Objects.isNull(connection)) {

            //获取properties对象
            properties = getProperties();

            //从properties中获取,url、user、password
            //properties文件,写入/读取,都是已key:value格式
            String url = properties.getProperty("jdbc.url");

            String user = properties.getProperty("jdbc.username");

            String password = properties.getProperty("jdbc.password");

            try {

                logger.info("***********开始连接数据库***********");
                logger.info("连接地址:[" + url + "]");
                logger.info("用户名:[" + user + "]");
                logger.info("密码:[" + password + "]");

                //获取数据库连接
                connection = DriverManager.getConnection(url, user, password);

            } catch (SQLException e) {
                logger.error("数据库连接失败,请检查链接地址、用户名、密码!");
                logger.error("报错内容:" + e);
            }
        }
        //返回Connection对象
        return connection;
    }
}

写好方法先测试,鼠标右击通过Generate生成case,在原有的JdbcQueryUtilTest中勾选要生成的case,执行测试代码如下:

package cn.Knife.Wework.Utils;

import org.testng.annotations.Test;

import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import static org.testng.Assert.*;

/**
 * @author Knife
 * @description
 * @createTime 2020-05-11 1:41
 */
public class JdbcQueryUtilTest {

    JdbcQueryUtil jdbcQueryUtil = JdbcQueryUtil.getInstance();

    @Test
    public void testGetProperties() {
        //获取一个properties对象
        Properties properties = jdbcQueryUtil.getProperties();
        System.out.println(properties.getProperty("jdbc.url"));
        System.out.println(properties.getProperty("jdbc.driver"));
        System.out.println(properties.getProperty("jdbc.username"));
        System.out.println(properties.getProperty("jdbc.password"));
    }

    @Test
    public void testJdbcQueryAndUpdate() {
        HashMap<String, String> sql = new HashMap<String, String>() {{
            put("$.query", "select * from apiautotestparamter where module = 'login' and api_Uri = 'login_by';");
        }};
        HashMap<String, String> stringStringHashMap = jdbcQueryUtil.jdbcQueryAndUpdate(sql);
        for (Map.Entry<String, String> entry : stringStringHashMap.entrySet()) {
            System.out.println(entry.getKey());
            System.out.println(entry.getValue());
        }
    }
}

第一次执行结果,在获取字段的时候String queryField = metaData.getColumnLabel(i);执行报错,因为索引问题,他直接从1开始,这里需要修改一条代码;
image.png

//修改内容:123条代码
String queryField = metaData.getColumnLabel(i + 1);

第二次执行,queryValue赋值的时候报了空指针,因为查询到的字段没值,这里修改一下赋值操作;

image.png

//修改内容:修改第128条代码
queryValue = Objects.nonNull(resultSet.getObject(queryField)) ? 
     (resultSet.getObject(queryField).toString()) : 
        "";

修改后,查询执行成功:
image.png
修改更新数据库的case,代码如下:

     @Test
    public void testUpdate() {
        HashMap<String, String> sql = new HashMap<String, String>() {{
            put("$.update","UPDATE apiautotestparamter \n" +
                    "SET api_Name = '我们开始修改啦啊啦啦啦' \n" +
                    "WHERE\n" +
                    "\tapi_Type = 'post_json' \n" +
                    "\tAND case_Id = 1;");
        }};
        jdbcQueryUtil.jdbcQueryAndUpdate(sql);
    }

执行结果,数据库验证,如下图演示:
image.png
数据库修改验证:
image.png

怕大家改错代码,我还是把JdbcQueryUtil修改后的源码附上把,如下:

package cn.Knife.Wework.Utils;

import com.alibaba.fastjson.JSON;
import org.apache.log4j.Logger;

import java.io.IOException;
import java.sql.*;
import java.util.HashMap;
import java.util.Objects;
import java.util.Properties;

/**
 * @author Knife
 * @description
 * @createTime 2020-05-11 0:21
 */
public class JdbcQueryUtil {

    //properties工具
    private static Properties properties;

    //jdbc配置文件路径
    private static final String JDBC_PATH = "/conf/jdbc.properties";

    //添加当前对象到日志容器
    private static Logger logger = Logger.getLogger(JdbcQueryUtil.class);

    //获取数据库连接对象
    private static Connection connection;

    private static JdbcQueryUtil jdbcQueryUtil;


    /**
     * 获取jdbcQueryUtil对象
     *
     * @return
     */
    public static JdbcQueryUtil getInstance() {
        if (Objects.isNull(jdbcQueryUtil)) {
            jdbcQueryUtil = new JdbcQueryUtil();
        }
        return jdbcQueryUtil;
    }

    /**
     * 初始化,加载properties
     *
     * @return
     */
    public static Properties getProperties() {
        //如果properties为null,则创建
        if (Objects.isNull(properties)) {
            properties = new Properties();
        }

        //通过内部代码块加载properties配置内容
        {
            if (Objects.nonNull(properties)) {
                try {
                    properties.load(JdbcQueryUtil.class.getResourceAsStream(JDBC_PATH));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return properties;
    }

    /**
     * 修改或查询数据库
     * map中key的表示:修改:"$.update",查询:"$.query"
     *
     * @param sql
     * @return 返回每个字段查询到的结果集
     */
    public static HashMap<String, String> jdbcQueryAndUpdate(HashMap<String, String> sql) {
        //定义储存查询结果的容器
        HashMap<String, String> queryValues = new HashMap<>();

        //获取数据库连接
        connection = getConnection();

        //sql不为空,开始提供操作
        if (Objects.nonNull(sql)) {

            //定义PreparedStatement对象,此对象有操作数据库的方法
            PreparedStatement preparedStatement;
            try {
                //判断当前是更新还是查询数据库
                if (Objects.nonNull(sql.get("$.update"))) {

                    logger.info("开始执行更新数据操作!");

                    //如果是更新执行更新操作
                    preparedStatement = connection.prepareStatement(sql.get("$.update"));
                    //执行修改操作
                    preparedStatement.executeUpdate();

                    logger.info("更新成功,执行的sql:" + sql.get("$.update"));
                }

                if (Objects.nonNull(sql.get("$.query"))) {

                    logger.info("开始执行查询操作!");

                    preparedStatement = connection.prepareStatement(sql.get("$.query"));

                    //执行查询,获得结果集
                    ResultSet resultSet = preparedStatement.executeQuery();

                    //获取查询的相关信息
                    ResultSetMetaData metaData = resultSet.getMetaData();

                    //获取查询字段的数值
                    int columnCount = metaData.getColumnCount();

                    while (resultSet.next()) {
                        //循环取出每个字段的值
                        for (int i = 0; i < columnCount; i++) {

                            //获取查询字段 //
                            String queryField = metaData.getColumnLabel(i + 1);
                            String queryValue = null;
                            //判断当前字段是否为空
                            if (Objects.nonNull(queryField)) {
                                //如果不为空,取出查询到的value
                                queryValue =
                                        Objects.nonNull(resultSet.getObject(queryField)) ?
                                                (resultSet.getObject(queryField).toString()) : "";
                            }

                            //将查询到的结果添加到map
                            queryValues.put(queryField, queryValue);

                            logger.info("当前执行查询sql:" + sql.get("$.query"));
                            logger.info("查询字段:" + queryField);
                            logger.info("查询结果:" + queryValue);
                        }
                    }
                    //返回查询到的结果集
                    return queryValues;
                }
            } catch (SQLException e) {
                logger.error("出现报错,请检查sql集:" + JSON.toJSONString(sql));
                logger.error("报错内容:" + e);
                e.printStackTrace();
            } finally {

                logger.info("开始关闭数据库连接");

                if (connection != null) {
                    try {
                        connection.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return null;
    }

    /**
     * 获取数据库连接
     *
     * @return
     */
    private static Connection getConnection() {
        if (Objects.isNull(connection)) {

            //获取properties对象
            properties = getProperties();

            //从properties中获取,url、user、password
            //properties文件,写入/读取,都是已key:value格式
            String url = properties.getProperty("jdbc.url");

            String user = properties.getProperty("jdbc.username");

            String password = properties.getProperty("jdbc.password");

            try {

                logger.info("\n***********开始连接数据库***********"
                        + "\n连接地址:[" + url + "]"
                        + "\n用户名:[" + user + "]"
                        + "\n密码:[" + password + "]");

                //获取数据库连接
                connection = DriverManager.getConnection(url, user, password);

            } catch (SQLException e) {
                logger.error("数据库连接失败,请检查链接地址、用户名、密码!");
                logger.error("报错内容:" + e);
            }
        }
        //返回Connection对象
        return connection;
    }
}

今天实在超晚了,明天把用法可以和大家演示,或者大伙有自己使用的用途;
哈哈,凌晨3.50了呢;
image.png
大家可能会疑惑为什么我每次截图都出现一个大大360加速图标,因为电脑右边屏坏了,坏了,坏了!!!!
image.png

5.1重新设计一下我们昨天封装的查询类:

1).修改后的JdbcQueryUtilJdbcQueryUtil类,代码如下:

import org.apache.log4j.Logger;
import java.io.IOException;
import java.sql.*;
import java.util.*;

/**
 * @author Knife
 * @description 执行sql
 * @createTime 2020-05-11 0:21
 */
public class JdbcQueryUtil extends Super_Utils {

    //properties工具
    private static Properties properties;

    //jdbc配置文件路径
    private static final String JDBC_PATH = "/conf/jdbc.properties";

    //添加当前对象到日志容器
    private static Logger logger = Logger.getLogger(JdbcQueryUtil.class);

    //获取数据库连接对象
    private static Connection connection;


    /**
     * 初始化,加载properties
     *
     * @return
     */
    private static Properties getProperties() {
        //如果properties为null,则创建
        if (Objects.isNull(properties)) {
            properties = new Properties();
        }

        //通过内部代码块加载properties配置内容
        {
            try {
                properties.load(JdbcQueryUtil.class.getResourceAsStream(JDBC_PATH));
            } catch (IOException e) {

                logger.error("文件读取异常,清检查读写路径!");
                logger.error("报错内容:" + e);
            }
        }
        return properties;
    }


    /**
     * 修改或查询数据库
     * map中key的表示:修改:"$.update",查询:"$.query"
     *
     * @param sqlsMap 写入要获取的字段值
     * @return 返回每个字段查询到的结果集
     */
    public static List<HashMap<String, Object>> jdbcQueryAndUpdate(HashMap<String, List<String>> sqlsMap) {

        //定义储存查询结果的容器
        HashMap<String, Object> queryValue;
        List<HashMap<String, Object>> queryVlues;

        if (Objects.nonNull(sqlsMap)) {

            //定义PreparedStatement对象,此对象有操作数据库的方法
            PreparedStatement preparedStatement;

            try {

                //获取数据库连接
                connection = getConnection();

                //判断当前是更新还是查询数据库
                if (Objects.nonNull(sqlsMap.get("$.update"))) {

                    logger.info("开始执行更新数据操作!");

                    //从list中取出需要执行的sql
                    List<String> list = sqlsMap.get("$.update");

                    for (String update_sql : list) {
                        //如果是更新执行更新操作
                        preparedStatement = connection.prepareStatement(update_sql);
                        //执行修改操作
                        preparedStatement.executeUpdate();

                        logger.info("更新成功,执行的sql:" + update_sql);
                    }
                }

                if (Objects.nonNull(sqlsMap.get("$.query"))) {

                    logger.info("开始执行查询操作!");
                    List<String> list = sqlsMap.get("$.query");

                    //每次入query都重新创建容器
                    queryVlues = new ArrayList<>();

                    for (String query_sql : list) {

                        preparedStatement = connection.prepareStatement(query_sql);

                        //执行查询,获得结果集
                        ResultSet resultSet = preparedStatement.executeQuery();

                        //获取查询的相关信息
                        ResultSetMetaData metaData = resultSet.getMetaData();

                        //获取查询字段的数值
                        int columnCount = metaData.getColumnCount();

                        //没执行一次查询new一次map,所以不用担心里面的值混乱
                        queryValue = new HashMap<>();

                        while (resultSet.next()) {
                            //循环取出每个字段的值
                            for (int i = 0; i < columnCount; i++) {

                                //获取查询字段 //
                                String queryField = metaData.getColumnLabel(i + 1);
                                String value = null;
                                //判断当前字段是否为空
                                if (Objects.nonNull(queryField)) {
                                    //如果不为空,取出查询到的value
                                    value =
                                            Objects.nonNull(resultSet.getObject(queryField)) ?
                                                    (resultSet.getObject(queryField).toString()) : "";
                                }

                                //将查询到的结果添加到map
                                queryValue.put(queryField, value);

                            }
                        }

                        logger.info("\n当前执行查询sql:\n" + query_sql
                                + "\n查询结果:" + queryValue);

                        queryVlues.add(queryValue);
                    }
                    return queryVlues;
                }
            } catch (SQLException e) {

                logger.error("sql执行异常,清检查sql语句!");
                logger.error("报错内容:" + e);
                e.printStackTrace();
            } finally {

                //关闭连接,释放资源
                if (Objects.nonNull(connection)) {
                    try {
                        connection.close();
                    } catch (SQLException e) {

                        logger.error("资源关闭异常!");
                        logger.error("报错内容:" + e);
                    }
                }
            }
        }
        return null;
    }


    /**
     * 获取数据库连接
     *
     * @return
     */
    private static Connection getConnection() {

        if (Objects.isNull(connection)) {

            //获取properties对象
            properties = getProperties();

            //从properties中获取,url、user、password
            //properties文件,写入/读取,都是已key:value格式
            String url = properties.getProperty("jdbc.url");

            String user = properties.getProperty("jdbc.username");

            String password = properties.getProperty("jdbc.password");

            try {

                logger.info("\n***********开始连接数据库***********"
                        + "\n连接地址:[" + url + "]"
                        + "\n用户名:[" + user + "]"
                        + "\n密码:[" + password + "]");

                //获取数据库连接
                connection = DriverManager.getConnection(url, user, password);

            } catch (SQLException e) {

                logger.error("数据库连接失败,请检查连接地址、用户名、密码!");
                logger.error("报错内容:" + e);
            }
        }
        //返回Connection对象
        return connection;
    }
}

2).在封装一个jdbcUtil方法,通过jdbcUtil控制JdbcQueryUtilJdbcQueryUtil的sql执行,代码如下:

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;

/**
 * @author Knife
 * @description 批量/单条sql执行工具类:基于JdbcQueryUtil重新封装,控制执行
 * @createTime 2020-05-11 18:11
 */
public class JdbcUtil extends JdbcQueryUtil {

    private static JdbcUtil jdbcUtil;

    /**
     * 获取jdbcUtil
     *
     * @return
     */
    public static JdbcUtil getInstance() {
        if (Objects.isNull(jdbcUtil)) {
            jdbcUtil = new JdbcUtil();
        }
        return jdbcUtil;
    }


    /**
     * 批量执行sql查询操作
     *
     * @param sqls
     * @return
     */
    public static List<HashMap<String, Object>> jdbcQueryAll(List<String> sqls) {
        HashMap<String, List<String>> querys = new HashMap<String, List<String>>() {{
            put("$.query", sqls);
        }};
        return jdbcQueryAndUpdate(querys);
    }


    /**
     * 批量执行修改操作
     *
     * @param sqls
     */
    public static void jdbcUpdateAll(List<String> sqls) {
        HashMap<String, List<String>> querys = new HashMap<String, List<String>>() {{
            put("$.update", sqls);
        }};
        jdbcQueryAndUpdate(querys);
    }


    /**
     * 执行单条sql查询操作
     * @param sql
     * @return
     */
    public static HashMap<String,Object> query(String sql) {
        List<String> list = Arrays.asList(sql);
        List<HashMap<String, Object>> contens = jdbcQueryAll(list);
        if (Objects.nonNull(contens)) {
            return contens.get(0);
        }
        return null;
    }

    /**
     * 执行单条sql修改操作
     * @param sql
     * @return
     */
    public static void update(String sql) {
        List<String> list = Arrays.asList(sql);
        jdbcUpdateAll(list);
    }
}

写 case测试一下我们写的代码,怎么生成我不说了,case代码如下:

import org.testng.annotations.Test;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;


/**
 * @author Knife
 * @description 测试jdbcUtil的实现
 * @createTime 2020-05-11 18:16
 */
public class JdbcUtilTest {

    private static jdbcUtil jdbcUtil = JdbcUtil.getInstance();

    @Test
    public void testQuery() {
        String sql = "select * from apiautotestparamter where case_id = 6;";
        HashMap<String, Object> query = jdbcUtil.query(sql);
        System.out.println(query);
    }

    @Test
    public void testQueryAll() {
        String sql = "select * from apiautotestparamter where case_id = 6;";

        List<String> list = Arrays.asList(sql, "select api_Name from apiautotestparamter where case_id = 1;");
        jdbcUtil.jdbcQueryAll(list);
    }


    @Test
    public void testUpdate() {
        String updateSql = "UPDATE apiautotestparamter \n" +
                "SET api_Name = '修改测试' \n" +
                "WHERE\n" +
                "\tapi_Type = 'post_json' \n" +
                "\tAND case_Id = 1;";

        jdbcUtil.update(updateSql);
    }

    @Test
    public void testUpdateAll() {
        String updateSql = "UPDATE apiautotestparamter \n" +
                "SET api_Name = '批量修改测试1' \n" +
                "WHERE\n" +
                "\tapi_Type = 'post_json' \n" +
                "\tAND case_Id = 1;";


        List<String> list = Arrays.asList(updateSql, "UPDATE apiautotestparamter \n" +
                "SET api_Name = '批量修改2' \n" +
                "WHERE\n" + "case_Id = 6;");

        jdbcUtil.jdbcUpdateAll(list);
    }
}

testQuery单条查询测试,执行结果:image.png
testQueryAll多查询测试,执行结果:
image.png
testUpdate单条修改数据测试,执行结果:
image.png
数据库中,查看我们修改的内容:
image.png
testUpdateAll多条修改sql执行测试,执行结果:
image.png
数据库中验证修改内容,如下:
image.png

5.2在我们进行自动化的过程中,如何使用我们封装的JdbcUtil类呢?如下:

1).新建一个类,命名VariablesUtil,专门用于获取数据库中的接口参数依赖,代码如下:

package cn.Knife.Wework.VariablesStore;

import cn.Knife.Wework.Utils.JdbcUtil;
import org.apache.log4j.Logger;

import java.util.HashMap;

/**
 * @author Knife
 * @description
 * @createTime 2020-05-11 21:09
 */
public class VariablesUtil extends JdbcUtil {

    private static Logger logger = Logger.getLogger(VariablesUtil.class);

    /**
     * 从数据库中获取变量
     *
     * @return
     */
    public static String getDepartmentName() {

        logger.info("开始从数据库获取变量!");

        String sql = "select  api_Name from apiautotestparamter where case_Id = 1";
        HashMap<String, Object> query = query(sql);

        logger.info("获取到的变量值:" + query.get("api_Name").toString());

        return query.get("api_Name").toString();
    }
}

2).怎么使用我们的VariablesUtil类,如何将JdbcUtil类用起来呢?这里做一个简单的示例哈,通过Generate生成case,代码如下:

import cn.Knife.Wework.AddressBookManagement.Department;
import cn.Knife.Wework.VariablesStore.VariablesUtil;
import io.restassured.response.ValidatableResponse;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;

import java.util.HashMap;

import static org.hamcrest.Matchers.equalTo;

/**
 * @author Knife
 * @description
 * @createTime 2020-05-11 21:26
 */
public class VariablesTest {
    private static Department department = new Department();

    private static VariablesUtil variablesUtil = new VariablesUtil();

    @BeforeTest
    public void setUp() {
        department.deleteAll();
    }

    @Test
    public void getMysqlVariableTest() {

        //从数据库中获取某个值,如果接口依赖这个值,比如:消费卷id,商品code;
        String name = variablesUtil.getDepartmentName();

        //执行查询
        HashMap<String, Object> query = department.query("select api_Name from apiautotestparamter where case_Id = 8;");
        //从map中取出查询到的数据
        int aset = Integer.parseInt(query.get("api_Name").toString());

        department.create(name, "111", "1", "1000", "1010")
                //如果我们执行的用例需要数据库断言??
                //或者执行后写条sql进行结果校验
                .then().body("errcode", equalTo(aset));    //断言接口响应结果的字段,与数据库值一致
    }
}

case执行结果:
image.png
数据库中我提前造了一条数据,亲们可根据公司实际业务需求去写sql:
image.png
3).使用Java多态的属性,我们再对JdbcUtil封装一层,自己手写一个占位符替换,代码如下:

import org.apache.log4j.Logger;
import java.util.*;

/**
 * @author Knife
 * @description 批量/单条sql执行工具类:基于JdbcQueryUtil重新封装,控制执行
 * @createTime 2020-05-11 18:11
 */
public class JdbcUtil extends JdbcQueryUtil {

    private static JdbcUtil jdbcUtil;

    private static Logger logger = Logger.getLogger(JdbcUtil.class);

    /**
     * 获取jdbcUtil
     *
     * @return
     */
    public static JdbcUtil getInstance() {
        if (Objects.isNull(jdbcUtil)) {
            jdbcUtil = new JdbcUtil();
        }
        return jdbcUtil;
    }


    /**
     * 批量执行sql查询操作
     *
     * @param sqls
     * @return
     */
    public static List<HashMap<String, Object>> jdbcQueryAll(List<String> sqls) {
        HashMap<String, List<String>> querys = new HashMap<String, List<String>>() {{
            put("$.query", sqls);
        }};
        return jdbcQueryAndUpdate(querys);
    }


    /**
     * 批量执行修改操作
     *
     * @param sqls
     */
    public static void jdbcUpdateAll(List<String> sqls) {
        HashMap<String, List<String>> querys = new HashMap<String, List<String>>() {{
            put("$.update", sqls);
        }};
        jdbcQueryAndUpdate(querys);
    }


    /**
     * 执行单条sql查询操作
     *
     * @param sql
     * @return
     */
    public static HashMap<String, Object> query(String sql) {
        List<String> list = Arrays.asList(sql);
        List<HashMap<String, Object>> contens = jdbcQueryAll(list);
        if (Objects.nonNull(contens)) {
            return contens.get(0);
        }
        return null;
    }

    /**
     * 执行单条sql查询操作,根据占位符"?",替换值为args
     *
     * @param sql
     * @param args
     * @return
     */
    public static HashMap<String, Object> query(String sql, Object... args) {
        //替换占位符为指定值
        String formatSql = strFormatByArgs(sql, args);
        List<HashMap<String, Object>> contens = jdbcQueryAll(Arrays.asList(formatSql));
        if (Objects.nonNull(contens)) {
            return contens.get(0);
        }
        return null;
    }

    /**
     * 执行单条sql修改操作
     *
     * @param sql
     * @return
     */
    public static void update(String sql) {
        List<String> list = Arrays.asList(sql);
        jdbcUpdateAll(list);
    }


    /**
     * 执行单条sql修改操作,根据占位符"?",替换值为args
     *
     * @param sql  执行的sql语句
     * @param args 可变形参
     */
    public static void update(String sql, Object... args) {
        //替换占位符为指定值
        String formatSql = strFormatByArgs(sql, args);
        List<String> list = Arrays.asList(formatSql);
        jdbcUpdateAll(list);
    }


    /**
     * 将占位符替换成可变形参值,格式化sql语句
     *
     * @param sql  sql
     * @param args 需要替换的参数
     * @return
     */
    public static String strFormatByArgs(String sql, Object... args) {

        //循环可变形参
        for (int i = 0; i < args.length; i++) {

            //如果可变形参中出现String,则加上单引号,直接传string数据库不识别的
            if (args[i] instanceof String) {
                args[i] = "\'" + args[i] + "\'";
            }
        }

        //比较是否出现占位符
        if (sql.contains("?")) {
            //将占位符替换成"%s"
            sql = sql.replace("?", "%s");
        }

        //通过format格式化参数,进行赋值操作
        String formatSQL = String.format(sql, args);

        logger.info("格式化前SQL:" + sql);
        logger.info("占位符替换后SQL:" + formatSQL);

        return formatSQL;
    }
}

在代码中引用方法时可以清楚看见,有两个方法名相同,参数不同的方法让我们选择。这是Java三大特性之一,多态:同名不同参
image.png
再已有class,新增一个case,代码如下:

    @Test
    public void jdbcUtilArgsTest() {
        JdbcUtil jdbcUtil = new JdbcUtil();
        String sql = "select * from apiautotestparamter where case_id = ? and api_Name = ?;";
        jdbcUtil.query(sql, 1, "批量修改测试1");
    }

执行结果:
image.png

5.3邮件发送:执行完case自动发送测试报告到指定邮箱:

1).加入pom依赖:

    <!-- https://mvnrepository.com/artifact/javax.mail/mail -->
        <dependency>
            <groupId>javax.mail</groupId>
            <artifactId>mail</artifactId>
            <version>1.4.7</version>
        </dependency>

2).在Rest_Perfect类中增加一个方法,获取系统时间:

     /**
     * 获取年-月-日系统时间
     *
     * @return
     */
    public static String getFormatDateTime() {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy月MM月dd日 HH:mm:ss");
        Date date = new Date();
        return dateFormat.format(date);
    }

3).增加一个工具类,取名SendEmail,代码如下:

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import org.apache.log4j.Logger;
import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.activation.FileDataSource;
import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import java.io.*;
import java.util.*;

/**
 * @author Knife
 * @description 发送自动化测试报告
 * @createTime 2020-05-13 21:53
 */
public class SendEmail extends Rest_Perfect {

    private static Logger logger = Logger.getLogger(SendEmail.class);

    private static SendEmail sendEmail;

    /**
     * 获取sendEmail对象
     *
     * @return
     */
    public static SendEmail getInstance() {
        if (Objects.isNull(sendEmail)) {
            sendEmail = new SendEmail();
        }
        return sendEmail;
    }


    /**
     * 发送邮件
     *
     * @param title          邮件标题
     * @param filePath       allure-report中suite.json文件路径
     * @param addresseeEmail 收件邮箱
     */
    public static void send(String title, String filePath, String addresseeEmail) {

        //根据运行测试类,和main入口判断
        String p = SendEmail.class.getResource("/").getPath();
        String[] split = p.split("/");
        for (String s : split) {
            if ("test-classes".equals(s)) {
                filePath = p.substring(0, p.indexOf("/test-classes")) + filePath;
            }
            if ("classes".equals(s)) {
                filePath = p.substring(0, p.indexOf("/classes")) + filePath;

            }
        }
        //获取allure报告内容
        String sendEmailText = getAllureResult(filePath);

        // 创建一个Property文件对象
        Properties props = new Properties();

        // 设置邮件服务器的信息,这里设置smtp主机名称
        props.put("mail.smtp.host", "smtp.163.com");

        // 设置socket factory 的端口
        props.put("mail.smtp.socketFactory.port", "465");

        // 设置socket factory
        props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");

        // 设置需要身份验证
        props.put("mail.smtp.auth", "true");

        // 设置SMTP的端口,QQ的smtp端口是25
        props.put("mail.smtp.port", "25");

        // 身份验证实现
        Session session = Session.getDefaultInstance(props, new Authenticator() {

            protected PasswordAuthentication getPasswordAuthentication() {
                // 第二个参数,就是我网易邮箱开启smtp的授权码
                return new PasswordAuthentication("写自己邮箱@163.com", "***写自己的授权码");

            }

        });

        try {

            // 创建一个MimeMessage类的实例对象
            Message message = new MimeMessage(session);

            // 设置发件人邮箱地址
            message.setFrom(new InternetAddress("写自己邮箱@163.com"));

            // 设置收件人邮箱地址
            message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(addresseeEmail));

            // 设置邮件主题
            title = getFormatDateTime() + " " + title;
            message.setSubject(title);

            // 创建一个MimeBodyPart的对象,以便添加内容
            BodyPart messageBodyPart1 = new MimeBodyPart();

            // 设置邮件正文内容
            messageBodyPart1.setText(sendEmailText);

            // 创建另外一个MimeBodyPart对象,以便添加其他内容
            MimeBodyPart messageBodyPart2 = new MimeBodyPart();

            // 创建一个datasource对象,并传递文件
            DataSource source = new FileDataSource(filePath);

            // 设置handler
            messageBodyPart2.setDataHandler(new DataHandler(source));

            // 加载文件
            /*messageBodyPart2.setFileName(filePath);*/

            // 创建一个MimeMultipart类的实例对象
            Multipart multipart = new MimeMultipart();

            // 添加正文1内容
            multipart.addBodyPart(messageBodyPart1);

            // 添加正文2内容
            //multipart.addBodyPart(messageBodyPart2);

            // 设置内容
            message.setContent(multipart);

            // 最终发送邮件
            Transport.send(message);


            logger.info("\n邮件主题:" + title +
                    "\n邮件内容:" + sendEmailText +
                    "\n收件人邮箱:" + addresseeEmail +
                    "\n****************************邮件发送成功****************************");

        } catch (MessagingException e) {

            logger.error("邮件发送异常!");
            logger.error("报错为:" + e);
        }

    }


    /**
     * 解析allure-report中suite.json文件内容
     *
     * @param jsonFilePath
     * @return
     */
    public static String getAllureResult(String jsonFilePath) {

        //定义储存用例结果的容器
        StringBuffer suiteResult = new StringBuffer();

        //实例化suiteResult

        int p_sum = 0;  //执行成功case总数
        int f_sum = 0;  //执行失败case总数
        int timeSum = 0;    //运行总共花费时间

        //存储测试方法名
        Object caseName = null;
        try (FileInputStream fileInputStream = new FileInputStream(jsonFilePath)) {
            //获取documentContext对象
            DocumentContext documentContext = JsonPath.parse(fileInputStream);
            //操作documentContext读取json文件中的内容
            List<LinkedHashMap<String, Object>> caseList = documentContext.read("$.children[*].children[*].children[*].children[*]");

            //遍历获取到的所有测试类
            HashMap<String, Object> err_Map = new HashMap<>();
            for (LinkedHashMap<String, Object> linkedHashMap : caseList) {
                for (Map.Entry<String, Object> entry : linkedHashMap.entrySet()) {

                    //获取测试方法名
                    if ("name".equals(entry.getKey())) {
                        caseName = entry.getValue();
                    }

                    //获取case执行结果状态
                    if ("status".equals(entry.getKey())) {
                        if ("passed".equals(entry.getValue())) {
                            p_sum++;
                        } else if ("failed".equals(entry.getValue())) {
                            //记录是失败case
                            err_Map.put("err_Case" + (f_sum + 1), caseName);
                            f_sum++;
                        }
                    }
                    //获取case执行时间
                    if ("time".equals(entry.getKey())) {
                        entry.getValue();
                        Map<String, Object> timeMap = JSONObject.parseObject(JSON.toJSONString(entry.getValue()));
                        Integer duration = (Integer) timeMap.get("duration");
                        timeSum += duration;
                    }
                }
            }

            suiteResult.append("Case执行总数:" + (p_sum + f_sum));
            suiteResult.append("\n通过:" + p_sum + "条");
            suiteResult.append("\n失败:" + f_sum + "条");
            suiteResult.append((f_sum > 0 ? ("\n执行出错的Method:" + JSON.toJSONString(err_Map)) : ""));
            suiteResult.append("\n执行花费时间:" + (timeSum / 1000) + "秒");

            logger.info("allure-report解析成功");

            return suiteResult.toString();
        } catch (Exception e) {
            logger.error("请检查路径,获取json文件内容!");
            logger.error("报错内容:" + e);
        }
        return null;
    }
}

4).新建一个class,执行dos命令,将allure的json文件开启服务展示,转为html文件直接展示,代码如下:

package cn.Knife.Wework.Utils;

import org.apache.log4j.Logger;

import java.io.*;


/**
 * @author Knife
 * @description
 * @createTime 2020-05-13 15:49
 */
public class OutputDos extends Rest_Perfect {

    private static Logger logger = Logger.getLogger(OutputDos.class);

    private static String classPath = OutputDos.class.getResource("/").getPath();

    static {
        //获取target根路径
        classPath = classPath.substring(1, classPath.indexOf("test-classes"));
    }


    /**
     * 输入路径(从target根路径输入):删除目录下全部文件
     *
     * @param path
     */
    public static void delAllureResults(String path) {
        File file = new File(classPath + path);
        File[] files = file.listFiles();
        for (File f : files) {
            f.delete();
            logger.info(String.format("当前文件:%s,删除成功", f));
        }
    }

    /**
     * 输入路径(从target根路径输入):results和report得到allure得html报告
     *
     * @param rawPath
     * @param outPath
     */
    public static void allureGenerateHtmlReport(String rawPath, String outPath) {
        StringBuffer dos = new StringBuffer();
        dos.append(String.format("cmd /c allure generate %s -o %s --clean", (classPath + rawPath), (classPath + outPath)));
        //如果还有其他dos命令需要执行,以"&&"分割代表下一条
        //dos.append(String.format("&& start %s", (classPath + rawPath));
        try  {
            Process exec = Runtime.getRuntime().exec(dos.toString());
            //执行dos命令
            exec.waitFor();
        } catch (Exception e) {
            logger.error("文件路径错误,请重新输入!");
            logger.error("报错内容:" + e);
        }
    }
}

5).新建一个测试类取名BaseTest,用其它测试类继承它,代码如下:

import cn.Knife.Wework.Utils.OutputDos;
import cn.Knife.Wework.Utils.SendEmail;
import io.qameta.allure.Description;
import org.testng.annotations.AfterSuite;
import org.testng.annotations.BeforeSuite;

import java.util.Objects;

/**
 * @author Knife
 * @description
 * @createTime 2020-05-13 23:03
 */
public class BaseTest {

    private static OutputDos od;

    private static SendEmail sendEmail = SendEmail.getInstance();

    @Description("删除allure遗留的测试结果")
    @BeforeSuite
    public void be() {
        if (Objects.isNull(od)) {
            od = new OutputDos();
            //运行前删除allure遗留测试结果
            od.delAllureResults("/allure-results");
        }
    }


    @Description("解析allure.json文件为report,发送邮件")
    @AfterSuite
    public void af() {
        //解析allure的json文件为html
        od.allureGenerateHtmlReport("/allure-results", "/allure-report");

        sendEmail.send("接口自动化测试", "/allure-report/data/suites.json", "*****@qq.com");
    }
}

执行我们的testng.xml,运行结果:
image.png
执行完,直接自动发送邮件
image.png
展示一下,如果case执行出错,发送对应的方法名:
image.png

6.整个框架落地代码,公布如下:

1).github地址:git@github.com:p2538699146/restAssured-apiAutoTest.git
2).代码已经更新:
image.png

这篇博客已经到了尾声,非常感谢各位大佬百忙抽出时间观看,我可能代码写的潦草,准备写这篇博客的时候就考虑,代码从0开始写,一步一步搭建,巩固自己知识,也希望能帮到大家。

以后有机会,希望我能写出真正对大家有帮助的东西,那么废话不多说了,告辞!有缘江湖再见!!!!