接口测试实战

https://testerhome.com/topics/15301

https://testerhome.com/topics/18568

https://testerhome.com/topics/18514

 

https://github.com/reese0329/serviceTesting

 

 

 


企业微信接口测试实战



需求 


公司的接口一直不稳定影响用户使用(测试痛点) 
最近半年来出现了几次大的故障(质量反馈) 
每次升级都会影响老功能(回归测试) 
目前公司没有可靠的测试体系(测试策略)

微服务话改造要求有良好的测试体系保证(重构测试)


调研 


追查公司一来所有的故障原因,定位问题起因 
访谈CTO、产品经理、研发、测试、运维收集质量痛点

分析业务架构与流程调用

分析监控系统了解业务使用数据


测试计划与策略 


待测业务范围 
待测业务场景用例 
待测业务接口分析 

  • . Swagger文档 
  • .源代码
  • .代理抓包


业务用例梳理 

业务的输入

  • 启动配置
  • 请求输入
  • 第三方依赖


业务流程 

  • 传统基于需求的用例设计
  • 基于代码精准化的用例设计


业务输出

  • 借口响应
  • 中间状态数据

 

测试用例辅助设计

静态分析方法

  • 阅读文档:Swagger文档
  • 阅读代码:源代码

动态分析方法:

  • 动态抓包分析
  • 当太trace或者debug分析
  • 分析log日志和流量数据

接口测试实战_第1张图片

 

接口测试实战_第2张图片

接口测试实战_第3张图片

 

用例编写

  • 选择语言

Python

java

go kotlin

尽量使用与研发一直的编程语言和技术栈

 

  • 选择合适的测试框架 

Java + RestAssured + JUnit/TestNG 
Python + Requests + PyTest 
Python + HttpRunner 

.生成用例 + 编写用例


用例管理 


.通用业务对象封装 
.数据引用
.用例调试 
.用例执行策略 
.不同测试环境的切换


持续集成 


.触发执行

  • .什么时候(定时) 
  • .什么条件(特定环境部署完毕) 

.执.记录 
.结果报表 
.报警机制



用例更新 


.根据功能覆盖不断迭代用例


.业务覆盖导向 
.功能覆盖导向 
.输入覆盖导向 
.覆盖率导向


推动研发开展测试 


.集成测试有QA负责 
.模块级别的单元测试由研发负责 
.建设mock系统


质量监控 


.监控更多接口质量数据 
.建立全流程的质量保证体系 
.从线上数据建模用户行为 
.利用线上用户行为反补用例覆盖

 

 

企业微信接口测试实战

第一部分实战:

接口测试用例实现

第二部分实战:

测试框架改进与智能化测试

 

 

引擎封装

  • request
  • expect
  • 接口定义配置化数据化
  • 抽取通用测试框架
  • 支持多种格式 yaml 、 har、 swagger 、 wsdl

配置封装

  • 使用单例来维护全局配置
  • 读写配置文件

数据封装

  • 使用模板技术
  • 使用POJO 从研发的依赖lib提取是最好的
  • 使用json yaml对应的库进行修改

 

添加依赖

    
        com.jayway.jsonpath
        json-path
        2.4.0
    
import com.jayway.jsonpath.JsonPath;

 

接口测试实战_第4张图片

 

 

用例封装

  • 加载数据
  • 执行顺序
  • case管理
  • 用例之间减少依赖
  • 并发

 

引擎封装

接口测试实战_第5张图片

package com.ace.wework;

import io.restassured.response.Response;
import io.restassured.specification.RequestSpecification;

import java.util.HashMap;

import static io.restassured.RestAssured.given;

public class Restful {
    HashMap query=new HashMap();
    public RequestSpecification requestSpecification=given();
    public Response send(){
        requestSpecification=given().log().all();
        query.entrySet().forEach( entry-> {
            requestSpecification.queryParam(entry.getKey(), entry.getValue());
        });

        return requestSpecification.when().request("get","baidu.com");
    }



}

接口测试实战_第6张图片

package com.ace.wework.contact;

import com.ace.wework.Restful;
import com.ace.wework.Wework;

import static io.restassured.RestAssured.given;

public class Contact extends Restful {
    public Contact(){
        reset();
;    }
    //重置
    public void reset() {
        requestSpecification
                .log().all()
                .param("access_token", Wework.getToken())
                .expect().statusCode(200);
    }


}

 

 

 

使用map模型化数据

封装

    public Response create(HashMapmap){
        reset();          //reset 清空数据
        DocumentContext documentContext=JsonPath.parse(this.getClass().getResourceAsStream("/data/create.json"));
        map.entrySet().forEach(entry->{
            documentContext.set(entry.getKey(),entry.getValue());
        });

        return requestSpecification
                .body(documentContext.jsonString())
                .when().post("https://qyapi.weixin.qq.com/cgi-bin/department/create")
                .then().extract().response();
    }

 

 

    @Test
    void create1() {
        HashMapmap=new HashMap(){
            {put("name","test"+random);
            put("parentid","1");}
        };
        department.create(map).then().body("errcode",equalTo(0));
    }

 

 

 

0317_下午2

13:28

创建成员

创建模板-restful 读取jsonPath模板

    public static String template(String path, HashMapmap){
        DocumentContext documentContext= JsonPath.parse(Restful.class
                .getResourceAsStream(path));
        map.entrySet().forEach(entry->{
            documentContext.set(entry.getKey(),entry.getValue());
        });

        return documentContext.jsonString();

 

 

方法封装

package com.ace.wework.contact;

import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import io.restassured.response.Response;


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

public class Member extends Contact{


    public Response create(HashMapmap){
        String body=template("/data/Member.json",map);
        reset();
        return requestSpecification
                .body(body)
                .when().post("https://qyapi.weixin.qq.com/cgi-bin/user/create")
                .then().log().all().extract().response();

    }
}

 

封装测试用例

package com.ace.wework.contact;

import org.junit.jupiter.api.BeforeAll;

import org.junit.jupiter.api.Test;

import java.util.Arrays;
import java.util.HashMap;

import static org.hamcrest.Matchers.equalTo;
import static org.junit.jupiter.api.Assertions.*;

class MemberTest {

    static Member member;

    @BeforeAll
    static void setup(){
        member=new Member();

    }

    @Test
    void create() {
        HashMap map= new HashMap<>();
        map.put("userid","test0617"+member.random);
        map.put("name","test0617"+member.random);
        map.put("alias","test0617"+member.random);
        map.put("mobile","151"+member.random.substring(0,8));
        map.put("email",member.random.substring(0,8)+"@qq.com");
        map.put("department", Arrays.asList(1,2));
        member.create(map).then().statusCode(200).body("errcode",equalTo(0));
    }
}

 

 

测试数据

{
  "userid": "zhangsan",
  "name": "张三",
  "alias": "jackzhang",
  "mobile": "15913215421",
  "department": [1, 2],
  "order":[10,40],
  "position": "产品经理",
  "gender": "1",
  "email": "[email protected]",
  "is_leader_in_dept": [1, 0],
  "enable":1,
  "avatar_mediaid": null,
  "telephone": "020-123456",
  "address": "广州市海珠区新港中路",
  "extattr": {
    "attrs": [
      {
        "type": 0,
        "name": "文本名称",
        "text": {
          "value": "文本"
        }
      },
      {
        "type": 1,
        "name": "网页名称",
        "web": {
          "url": "http://www.test.com",
          "title": "标题"
        }
      }
    ]
  },
  "to_invite": true,
  "external_position": "高级产品经理",
  "external_profile": {
    "external_corp_name": null,
    "external_attr": [
      {
        "type": 0,
        "name": "文本名称",
        "text": {
          "value": "文本"
        }
      },
      {
        "type": 1,
        "name": "网页名称",
        "web": {
          "url": "http://www.test.com",
          "title": "标题"
        }
      },
      {
        "type": 2,
        "name": "测试app",
        "miniprogram": {
          "appid": "wx8bd80126147df384",
          "pagepath": "/index",
          "title": "waimai"
        }
      }
    ]
  }
}

 

 

Junit5数据驱动

添加依赖

    
        org.junit.jupiter
        junit-jupiter-params
        5.3.2
    

MemberTest.java

抽象userid为参数

接口测试实战_第7张图片

 

 

package com.ace.wework.contact;

import org.junit.jupiter.api.BeforeAll;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import java.util.Arrays;
import java.util.HashMap;

import static org.hamcrest.Matchers.equalTo;
import static org.junit.jupiter.api.Assertions.*;

class MemberTest {

    static Member member;

    @BeforeAll
    static void setup(){
        member=new Member();

    }

    @ParameterizedTest
    @ValueSource(strings ={"tester","ace"})
    void create(String name) {
        String nameNew=name+member.random;
        String random=String.valueOf(System.currentTimeMillis()).substring(5+0,5+8);
        HashMap map= new HashMap<>();
        map.put("userid",name+member.random);
        map.put("name",name+member.random);
        map.put("alias","test0617"+member.random);
        map.put("mobile","151"+random);
        map.put("email",random+"@qq.com");
        map.put("department", Arrays.asList(1,2));
        member.create(map).then().statusCode(200).body("errcode",equalTo(0));
    }
}

 

 

 

脏数据清理

删除全部部门

https://github.com/rest-assured/rest-assured/wiki/Usage#json-schema-validation

    public Response deleteAll(){
        reset();
        List idList=list("").then().extract().path("department.id"); 
//将部门列表转换成列表形式
        System.out.println(idList);   
        idList.forEach(id->delete(id.toString()));
        return null;
    }
    @Test
    void deleteAll() {
        department.deleteAll();
    }

 

优化:

    @BeforeEach
    void setUp() {
        if (department == null) {
            department = new Department();
            department.deleteAll();
        }
    }

 

用例封装

加载数据

执行顺序

case管理

 

 

 

 

 

断言

  • 尽量使用标准的JUnit TestNG标准框架
  • 断言尽量使用Harmcrest
  • 业务断言需要更完整,比如delete不能只断言errcode,还要去断言list返回的接口里的确没有了删除的内容

作业1

封装department的list,搜索特定id的部门,并在测试用例中断言i部门的d是否正确,把你的list的封装和测试用例代码贴到回复里

作业2

封装部门更新与删除,实现添加部门并删除部门 delete(),添加部门并更新部门 update() 贴update的封装与测试用例代码

封装

    public Response update(String name,String id){
        String body = JsonPath.parse(this.getClass().getResourceAsStream("/data/update.json"))
                .set("$.name",name)
                .set("$.id",id).jsonString();
        return given().log().all().queryParam("access_token",Wework.getToken())
                .body(body)
                .when().post("https://qyapi.weixin.qq.com/cgi-bin/department/update")
                .then().log().all().statusCode(200).extract().response();
    }

    public Response delete(String id){
        return given().log().all().queryParam("access_token",Wework.getToken())
                .queryParam("id",id)
                .when().get("https://qyapi.weixin.qq.com/cgi-bin/department/delete")
                .then().log().all().extract().response();
    }

update.json

{
  "id": 6,
  "name": "广州研发中心",
  "parentid": 1,
  "order": 1
}

 

测试用例

不依赖其他数据 修改或更新前先创建部门

 

    @Test
    void update() {
        String nameOld="HR_18";
        department.create(nameOld, "0616");
        String id=String.valueOf(department.list("").path("department.find{ it.name=='"+ nameOld +"'}.id"));
        department.update("HR061613", id)
                .then().body("errcode", equalTo(0));
    }

    @Test
    void delete()
//    {
//        department.create("python", "190616")
//                .then().body("errcode", equalTo(0));
//        department.delete("190616")
//                .then().body("errcode", equalTo(0));
//    }

    {
        String nameOld ="python2";
        department.create(nameOld, "1906162");
        String id = String.valueOf(department.list("").path("department.find{ it.name=='" + nameOld + "'}.id"));
        department.delete(id)
                .then().body("errcode", equalTo(0));
   }

 

或生成时间戳生成测试用例

    @Test
    void update() {
        String nameOld="HR_18"+random;
        department.create(nameOld);
        String id=String.valueOf(department.list("").path("department.find{ it.name=='"+ nameOld +"'}.id"));
        department.update("HR061613"+random, id)
                .then().body("errcode", equalTo(0));
    }

 

此处修改create方法中的参数只有name

此方法会产生很多的脏数据,注意处理

 

解决创建时,部门名称为中文的问题

接口测试实战_第8张图片

 

 

随机数据

class DepartmentTest {
    Department department;
    String random=String.valueOf(System.currentTimeMillis());

    @BeforeEach
    void setUp() {
        if (department == null) {
            department = new Department();
        }
    }

    @Test
    void list() {
        department.list("").then().statusCode(200)
                .body("department.name[0]", equalTo("ace"));
//        department.list("39").then().statusCode(200)
//                .body("department.name[0]",equalTo("ningningCenterdd"))
//        .body("department.id[0]",equalTo(39));

    }

    @Test
    void create() {
        department.create("测试"+random)
                .then().body("errcode", equalTo(0));
    }

 

 

 

 

 

 

 

 

 

作业3

完成成员管理的所有封装与测试用例,把自己的代码开源道github,并回帖自己的github地址

作业4

封装自己的读取yaml或者读取har格式并发送http请求的方法 templateFromHar or templateFromYaml,更新到github并贴地址。注明是哪个作业。比如
作业4 github文件地址

 

 

测试用例辅助设计

 

 

接口测试实战_第9张图片

右键copy all as fetch

创建*.har.json文件

接口测试实战_第10张图片

 

从html中读取测试内容

Restful中读取

    public  Response templateFromHar(String path,String pattern, HashMap map){
        //从har中读取请求,进行更新
        DocumentContext documentContext = JsonPath.parse(Restful.class
                .getResourceAsStream(path));
        map.entrySet().forEach(entry-> {
            documentContext.set(entry.getKey(), entry.getValue());
        });

        String method=documentContext.read("method:");
        String url=documentContext.read("url");

        return requestSpecification.when().request(method,url);
    }

 

Department中调用

    public Response update(HashMap map){
        return  templateFromHar("demo.har.json","https://work.weixin.qq.com/wework_admin/party?action=addparty",map);

    }

 

 

Swagger

https://petstore.swagger.io/?_ga=2.107822722.1416948099.1562839798-910621538.1562839798

https://petstore.swagger.io/v2/swagger.json

保存至

接口测试实战_第11张图片

 

 

 

你可能感兴趣的:(接口测试实战)