Skip to content

【线上】接口自动化测试

接口自动化测试

预习准备

  • 提前先预习完以下相关的知识,再开始本章节的学习。
  • 提前注册企业微信账号。
专题课 阶段 章节 Python 班级 Java 班级
接口自动化测试 L1 接口自动化测试框架介绍 录播地址 录播地址
接口自动化测试 L1 接口请求方法
接口自动化测试 L1 接口请求参数
接口自动化测试 L1 接口请求头
接口自动化测试 L1 接口请求体-json
接口自动化测试 L1 接口响应断言
接口自动化测试 L1 json 响应体断言
接口自动化测试 L2 多层嵌套响应断言
接口自动化测试 L2 【实战】宠物商店接口自动化测试实战
接口自动化测试 L3 整体结构响应断言
接口自动化测试 L3 数据库操作与断言
接口自动化测试 L4 多套被测环境

课程目标

  • 掌握接口自动化测试用例设计方法。
  • 掌握接口自动化测试中的各种格式的请求构造与响应断言技巧。
  • 掌握接口自动化测试中复杂断言方法。
  • L1:接口自动化测试用例设计
  • L2:接口请求构造与响应断言
  • L3:复杂断言与鉴权处理
  • L4:加解密与多套被测环境

接口自动化测试价值与体系

接口自动化测试价值与体系

知识点总结

点击查看:接口自动化测试知识点梳理.xmind

需求说明

  • 企业微信
    • 企业微信是腾讯微信团队打造的企业通讯与办公工具。
    • 具有与微信一致的沟通体验,丰富的 OA 应用,和连接微信生态的能力。
    • 可帮助企业连接内部、连接生态伙伴、连接消费者。专业协作、安全管理、人即服务。
  • 完成企业微信部门管理接口自动化测试。
  • 环境准备
    1. 企业微信注册。
    2. 企业微信白名单配置步骤:https://ceshiren.com/t/topic/22768

实战思路

uml diagram

需求分析

企业微信 API 文档

请求方式:GET/POST(HTTPS)
请求地址:https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=ID&corpsecret=SECRET
请求包体:
...
参数说明:
...
权限说明:
...
返回结果:
...
参数说明:
...

接口测试用例设计

编写接口自动化测试脚本

  • 单接口自动化测试
  • 接口业务流程自动化测试
单接口自动化测试

接口鉴权

接口鉴权是常见的一种安全机制,在本次实战中基本可以分为两部分。

  • 验证把认证接口需要的数据放入请求体中。
  • 认证后,验证成功获取到有效的 token 值。

后续的业务接口在请求后端服务时,都需要带上这个认证信息。

uml diagram

  • 获取企业微信通讯录管理接口的 access_token

uml diagram

  1. 获取接口调用凭证-企业 ID

    • Corpid(企业 ID)
    • 每个企业都拥有唯一的 corpid
    • 查看:管理后台【我的企业】--【企业信息】下查看【企业 ID】
  2. 获取接口调用凭证-Secret

    • Secret(应用的凭证密钥)
    • 通讯录管理 secret
    • 查看:【管理工具】--通讯录同步

Python 代码实现

def test_get_token(self):
    '''
    获取 access_token
    :return:
    '''
    # 定义凭证
    corpid = "xxx"
    corpsecret = "xxx"
    # 方式一:
    # url = f"https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={corpid}&corpsecret={corpsecret}"
    # 方式二:
    url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken"
    # 定义请求参数
    params = {
        "corpid": corpid,
        "corpsecret": corpsecret
    }
    # 发送 GET 请求
    r = requests.get(url)
    # 打印响应体
    print(r.text)

Java 代码实现

@Test
void getToken() {
    String corpid = "ww7f7d94d8b17ac268";
    String secret = "yFxjxy0uHfAl9QIjjAgwTiMTdjq16z-K9eSRfNZvqV4";
    Response response = given()
            //获取请求的日志 参数信息 请求头 请求参数
            .log().all()
            .param("corpid", corpid)
            .param("corpsecret", secret)
            .when()
            .get("https://qyapi.weixin.qq.com/cgi-bin/gettoken")
            .then()
            //获取响应日志 响应body 响应头信息
            .log().all()
            .extract().response();
    log.info(response.path("access_token").toString());
}

部门创建单接口自动化测试

Python 代码实现

  • 冒烟用例

    def test_create_department(self):
        '''
        创建部门
        :return:
        '''
        url = f"{self.base_url}/cgi-bin/department/create?access_token={self.token}"
        # 定义请求体
        data = {
            "name": "广州研发中心",
            "name_en": "RDGZ",
            "parentid": 1,
            "order": 1,
            "id": 210
        }
        # 发送 post 请求
        r = requests.post(url, json=data)
        print(r.text)
        # 断言接口状态
        assert r.status_code == 200
        # 断言接口响应体
        assert r.json().get("errcode") == 0
        # 断言部门创建的id
        ids = r.json().get('id')
        assert 210 == ids
    
  • 参数化运行

    @pytest.mark.parametrize(
        "name, name_en, parentid, order, depart_id, expect",
        [
            ("", "JISHU2", 1, 2, 3, 40058),
            ("j", "JISHU2", 1, 3, 4, 0),
            ("技术部23", "JISHU23", 1, 30, 1, 60123)
        ]
    )
    def test_create_department_by_params(self, name, name_en, parentid, order, depart_id, expect):
        '''
        参数化创建部门
        '''
        data = {
            "name": name,
            "name_en": name_en,
            "parentid": parentid,
            "order": order,
            "id": depart_id
        }
        url = f"{self.base_url}/cgi-bin/department/create?access_token={self.token}"
        # 发出 POST 请求
        r = requests.post(url, json=data)
        print(r.text)
        # 断言接口状态
        assert r.status_code == 200
        # 断言接口响应
        assert r.json().get("errcode") == expect
    

Java 代码实现

  • 冒烟用例
@Test
void createDepartment() {
    String reqJson = "{\n" +
            "\"name\":\"" + "技术部4" + "\",\n" +
            "\"name_en\":\"" + "JISHU4" + "\",\n" +
            "\"parentid\":" + 1 + ",\n" +
            "\"order\":" + 1 + ",\n" +
            "\"id\":" + 5 + "\n" +
            "}";
    Response response = given()
            .body(reqJson)
            .contentType(ContentType.JSON.withCharset(StandardCharsets.UTF_8)) // 设置请求的内容类型为 JSON,并指定字符编码为 UTF-8
            .log().all()
            .when()
            .post("https://qyapi.weixin.qq.com/cgi-bin/department/create?access_token=" + getToken())
            .then()
            .log().all()
            .statusCode(200) //断言状态码
            .body("errcode", is(0)).extract().response();
    Integer errcode = JsonPath.read(response.getBody().asString(), "$.errcode");
}
  • 参数化运行
@ParameterizedTest
@CsvSource({
        "技术部1,jishu1,1,1,2",
        "技术部2,jishu2,1,1,3",
        "技术部3,jishu3,1,1,4",
})
void createDepartmentParams(String name, String name_en, Integer parentid, Integer order, Integer id) {
    String reqJson = "{\n" +
            "\"name\":\"" + name + "\",\n" +
            "\"name_en\":\"" + name_en + "\",\n" +
            "\"parentid\":" + parentid + ",\n" +
            "\"order\":" + order + ",\n" +
            "\"id\":" + id + "\n" +
            "}";
    Response response = given()
            .body(reqJson)
            .contentType(ContentType.JSON.withCharset(StandardCharsets.UTF_8)) // 设置请求的内容类型为 JSON,并指定字符编码为 UTF-8
            .log().all()
            .when()
            .post("https://qyapi.weixin.qq.com/cgi-bin/department/create?access_token=" + getToken())
            .then()
            .log().all()
            .statusCode(200) //断言状态码
            .body("errcode", is(0))
            .extract().response();
}
接口业务流程测试
测试用例
测试模块 用例标题 前置条件 用例步骤 预期结果 实际结果
部门模块 创建重复部门 1. 登录成功
2. 已存在要添加的部门信息
1. 有存在部门的情况下,创建相同信息的部门失败
2. 删除部门
3. 查询部门删除成功
4. 再次创建部门
5. 查询部门创建成功
1.创建已有部门失败
2.删除后创建成功
重复创建部门流程测试

Python 实现

def test_create_department_exist(self):
    '''
    创建部门前,部门已存在,先删除,然后创建
    省略断言
    '''
    # 1. 准备已存在部门
    depart_id = 220
    create_data = {
        "name": "广州研发中心",
        "name_en": "RDGZ",
        "parentid": 1,
        "order": 1,
        "id": depart_id
    }
    r = requests.request("POST", self.create_url, json=create_data)
    # 准备已存在部门结果为 r.text
    # 2. 有存在部门的情况下,创建相同信息的部门失败
    r = requests.request("POST", self.create_url, json=create_data)
    # 3. 删除部门-->删除部门结果为  r.text
    params = {
        "id": depart_id
    }
    r = requests.request("GET", self.delete_url, params=params)
    # 4. 查询部门删除成功-->查询部门结果为r.text-->获取 id 列表
    r = requests.request("GET", self.list_url)
    id_list = [i['id'] for i in r.json().get("department_id")]
    # 5. 再次创建部门-->再次创建部门结果为 r.text
    r = requests.request("POST", self.create_url, json=create_data)
    # 6. 查询部门创建成功-->查询部门结果为 r.text-->获取 id 列表
    r = requests.request("GET", self.list_url)
    id_list = [i['id'] for i in r.json().get("department_id")]

Java 版本

    @Test
    void createDepartmentExist(){
        //省略具体代码实现
        // 1. 准备已存在部门
        ···
        // 2. 有存在部门的情况下,创建相同信息的部门失败
        ···
        // 3. 删除部门
        ···
        // 4. 查询部门删除成功
        ···
        // 5. 再次创建部门
        ···
        // 6. 查询部门创建成功
        ···
    }

接口测试框架封装

框架设计思路

项目结构

├── apis
│   ├── base_api
│   ├── department
│   └── wework
├── config
│   └── config.yaml
├── tests
│   ├── test_departments
│   └── test_department_flow
└── utils
    ├── log_utils
    └── utils
  • ApiObject 设计模式:
    • 封装。
    • 分层。
    • 把实现和测试用例以及断言进行拆分。

uml diagram

分层 作用 示例
base_api 封装和接口框架直接相关的方法 接口请求封装
wework 封装业务的全局操作 获取 token
获取配置数据
department 封装子模块的操作 部门信息的增删改查
test_department 测试步骤、业务流程及断言 添加部门用例

框架搭建

获取 access_token

企业微信特有逻辑,完成 access_token 的获取

  • Python 版本
class Wework:
    def __init__(self):
        self.base_url = "https://qyapi.weixin.qq.com"
        self.token = self.get_access_token()

    def get_access_token(self):
        '''
        获取 access_token
        :return:
        '''
        # 定义凭证
        corpid = "xxx"
        corpsecret = "xxx"
        url = f"{self.base_url}/cgi-bin/gettoken"
        # 定义请求参数
        params = {
            "corpid": corpid,
            "corpsecret": corpsecret
        }
        # 发送 GET 请求
        r = requests.get(url, params=params)
        # 打印响应体
        print(r.text)
        # 获取 access_token
        token = r.json().get("access_token")
        return token
  • Java 版本
@Slf4j
public class Wework {
    String CORPID;
    ConfigEntity config;

    public Wework() {
        // 读取配置
        config = ConfigUtil.getConfig();
        CORPID = config.getCorpId();
        RestAssured.baseURI = config.getBaseUrl();
    }

    String getToken(String sercret) {
        Response response = given()
                //获取请求的日志 参数信息 请求头 请求参数
                .param("corpid", CORPID)
                .param("corpsecret", sercret)
                .when()
                .get("/gettoken")
                .then()
                //获取响应日志 响应body 响应头信息
                .extract().response();
        return response.path("access_token").toString();
    }


}
封装对于业务模型

接口信息描述:只关注业务,不需要做断言。每个方法返回接口响应体。

  • python 版本
# 部门需要继承 Wework,这样就可以直接获取 Wework 中获取到的 access_token
class Department(Wework):

    def create(self, data):
        '''
        创建部门
        :return:
        '''
        create_url = f"{self.base_url}/cgi-bin/department/create?access_token={self.token}"
        r = requests.request(method="POST", url=create_url, json=data)
        return r
...
  • java 版本
public class WeworkDepartment extends Wework {
    String SECRET;

    public WeworkDepartment() {
        // 读取配置
        super();
        SECRET = config.getContactsSecret();
    }

    /**
     * 部门创建
     *
     * @param dce 创建部门数据实体类
     * @return
     */
    public Response create(DepartmentCreateEntity dce) {
        ···
    }

    /**
     * 部门删除
     * @param depart_id 部门id
     */
    public void delete(Integer depart_id) {
        ···
    }

    /**
     * 获取部门id列表
     * @return
     */
    public List<Integer> getDepartmentIds() {
        ···
    }


}
自动化测试用例实现
  • Python 版本
class TestDepartments:
    def setup_class(self):
        # 实例化部门类
        self.department = Department()
        # 准备测试数据
        self.depart_id = 220
        self.create_data = {
            "name": "广州研发中心",
            "name_en": "RDGZ",
            "parentid": 1,
            "order": 1,
            "id": self.depart_id
        }
        self.update_name = "广州研发中心-update"
        self.update_data = {
            "id": self.depart_id,
            "name": self.update_name
        }
    def test_department_flow(self):
        '''
        部门增删改查场景测试
        '''
        # 创建部门
        r = self.department.create(self.create_data)
        # 查询是否创建成功
        r = self.department.get()
        depart_ids = [i['id'] for i in r.json().get('department_id')]
        assert self.depart_id in depart_ids
        # 更新部门信息
        r = self.department.update(self.update_data)
        # 删除部门
        r = self.department.delete(self.depart_id)
        # 查询是否删除成功
        r = self.department.get()
        depart_ids = [i['id'] for i in r.json().get('department_id')]
        assert self.depart_id not in depart_ids
  • Java 版本
public class DepartmentApiObjectsTest {
    static WeworkDepartment weworkDepartment;

    @BeforeAll
    static void setUp() {
        weworkDepartment = new WeworkDepartment();
    }

    @Test
    void createDepartmentExist() {
        // 1. 准备已存在部门
        DepartmentCreateEntity createDepartment = new DepartmentCreateEntity("技术部", "JISHU1", 1, 1, 2);
        weworkDepartment.create(createDepartment);
        // 2. 有存在部门的情况下,创建相同信息的部门失败
        Response response = weworkDepartment.create(createDepartment);
        assertThat(response.path("errcode"), is(60008));
        // 3. 删除部门
        weworkDepartment.delete(createDepartment.getId());
        // 4. 查询部门删除成功
        List<Integer> ids = weworkDepartment.getDepartmentIds();
        assertThat(ids, not(hasItem(createDepartment.getId())));
        // 5. 再次创建部门
        weworkDepartment.create(createDepartment);
        // 6. 查询部门创建成功
        ids = weworkDepartment.getDepartmentIds();
        assertThat(ids, hasItem(createDepartment.getId()));
    }
}

框架优化

封装工具类
  • 创建 test_env.yaml,文件中配置和业务强相关的数据。
  • 创建工具类。
  • 修改 wework 中配置数据获取方式。

Python 实现

  • base_api.py 文件
def send_api(self,req):
    '''
    对 request 完成二次封装
    :param req:
    :return:
    '''
    ...
  • test.yaml 文件
# 接口自动化测试的基础服务地址抽离
base_url: https://xxx
# 和账户相关的信息
corpid:
  hogwarts: xxx
corp_secret:
  contacts: xxx
  • wework.py 文件
class Wework(BaseApi):
    def __init__(self):
        # 获取配置数据
        ...

    def get_token(self, secret):
        '''
        获取 token
        :return:
        '''
        ...
    def get_config(self):
        '''
        获取配置数据
        :return:
        '''
        ...
  • departments.py 文件
class Departments(Wework):
    def __init__(self):
        super().__init__()
        # 获取通讯录的secret
        ...

    def create(self, data):
        '''
        创建部门接口
        :param data: json 格式请求体
        :return:
        '''
        ...
多环境切换

Python 代码实现

class Wework(BaseApi):

    def __init__(self):
        ...

    def get_access_token(self):
        ....
    def get_config(self):
        '''
        获取配置数据
        :return:
        '''
        # 获取对于环境文件路径
        env = os.getenv('env', default='test')
        path = f"{Utils.get_root_path()}/config/{env}_env.yaml"

        yaml_data = Utils.get_yaml_data(path)
        # 获取需要的值
        ...

Java 代码实现

  • config.yaml文件
baseUrl: "https://qyapi.weixin.qq.com/cgi-bin"
corpId: "ww7f7d94d8b17ac268"
contactsSecret: "yFxjxy0uHfAl9QIjjAgwTiMTdjq16z-K9eSRfNZvqV4"
  • 配置实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ConfigEntity {
    private String baseUrl;
    private String corpId;
    private String contactsSecret;


}
  • 配置文件读取
public class ConfigUtil {

    /**
     * 获取项目配置文件
     *
     * @return 配置实体类
     */
    public static ConfigEntity getConfig() {
        try {
            ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
            ConfigEntity configEntity = mapper.readValue(ConfigEntity.class.getClassLoader().getResourceAsStream("config.yaml"), ConfigEntity.class);
            return configEntity;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
}
数据库配置

数据库配置

响应断言

多层嵌套响应断言

整体结构响应断言

课后练习

  • 完成接口自动化测试框架搭建
  • 添加获取部门成员 ID的场景
    • 添加复杂断言
      • 使用 JSONPath 断言
      • 使用 JSONSchema 断言
  • 添加多环境配置
  • 添加数据清理步骤
  • 添加日志
  • 添加测试报告
  • 数据库断言

总结

本次课程重点学习接口自动化测试的内容,包括单接口测试和业务流程测试脚本的编写,同时涉及搭建接口自动化测试框架及优化辅助测试。