接口自动化(一):基于RestAssured+Junit5实现接口自动化

HTTP协议


接口知识:

HTTP协议

对应上述请求过程:

  1. 浏览器向 DNS 服务器请求解析该 URL 中的域名所对应的 IP 地址;
  2. 解析出 IP 地址后,根据该 IP 地址和默认端口 80,和服务器建立TCP连接;
  3. 浏览器发出读取文件(URL 中域名后面部分对应的文件)的HTTP 请求,该请求报文作为 TCP 三次握手的第三个报文的数据发送给服务器;
  4. 服务器对浏览器请求作出响应,并把对应的 html 文本发送给浏览器;
  5. 释放 TCP连接;
  6. 浏览器将该 html 文本并显示内容;

由curl命令查看一个请求的全过程引入:

curl https://www.baidu.com -vvv
* Rebuilt URL to: https://www.baidu.com/
*   Trying 39.156.66.14...
* TCP_NODELAY set
* Connected to www.baidu.com (39.156.66.14) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, server accepted to use http/1.1
* Server certificate:
*  subject: C=CN; ST=beijing; L=beijing; OU=service operation department; O=Beijing Baidu Netcom Science Technology Co., Ltd; CN=baidu.com
*  start date: May  9 01:22:02 2019 GMT
*  expire date: Jun 25 05:31:02 2020 GMT
*  subjectAltName: host "www.baidu.com" matched cert's "*.baidu.com"
*  issuer: C=BE; O=GlobalSign nv-sa; CN=GlobalSign Organization Validation CA - SHA256 - G2
*  SSL certificate verify ok.
> GET / HTTP/1.1
> Host: www.baidu.com
> User-Agent: curl/7.58.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< Accept-Ranges: bytes
< Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
< Connection: keep-alive
< Content-Length: 2443
< Content-Type: text/html
< Date: Wed, 11 Mar 2020 13:07:36 GMT
< Etag: "588603eb-98b"
< Last-Modified: Mon, 23 Jan 2017 13:23:55 GMT
< Pragma: no-cache
< Server: bfe/1.0.8.18
< Set-Cookie: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/
< 
<!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css><title>百度一下,你就知道</title></head> <body link=#0000cc> <div id=wrapper> <div id=head> <div class=head_wrapper> <div class=s_form> <div class=s_form_wrapper> <div id=lg> <img hidefocus=true src=//www.baidu.com/img/bd_logo1.png width=270 height=129> </div> <form id=form name=f action=//www.baidu.com/s class=fm> <input type=hidden name=bdorz_come value=1> <input type=hidden name=ie value=utf-8> <input type=hidden name=f value=8> <input type=hidden name=rsv_bp value=1> <input type=hidden name=rsv_idx value=1> <input type=hidden name=tn value=baidu><span class="bg s_ipt_wr"><input id=kw name=wd class=s_ipt value maxlength=255 autocomplete=off autofocus=autofocus></span><span class="bg s_btn_wr"><input type=submit id=su value=百度一下 class="bg s_btn" autofocus></span> </form> </div> </div> <div id=u1> <a href=http://news.baidu.com name=tj_trnews class=mnav>新闻</a> <a href=https://www.hao123.com name=tj_trhao123 class=mnav>hao123</a> <a href=http://map.baidu.com name=tj_trmap class=mnav>地图</a> <a href=http://v.baidu.com name=tj_trvideo class=mnav>视频</a> <a href=http://tieba.baidu.com name=tj_trtieba class=mnav>贴吧</a> <noscript> <a href=http://www.baidu.com/bdorz/login.gif?login&amp;tpl=mn&amp;u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1 name=tj_login class=lb>登录</a> </noscript> <script>document.write('<a href="http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u='+ encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ '" name="tj_login" class="lb">登录</a>');
                </script> <a href=//www.baidu.com/more/ name=tj_briicon class=bri style="display: block;">更多产品</a> </div> </div> </div> <div id=ftCon> <div id=ftConw> <p id=lh> <a href=http://home.baidu.com>关于百度</a> <a href=http://ir.baidu.com>About Baidu</a> </p> <p id=cp>&copy;2017&nbsp;Baidu&nbsp;<a href=http://www.baidu.com/duty/>使用百度前必读</a>&nbsp; <a href=http://jianyi.baidu.com/ class=cp-feedback>意见反馈</a>&nbsp;京ICP证030173号&nbsp; <img src=//www.baidu.com/img/gs.gif> </p> </div> </div> </div> </body> </html>
* Connection #0 to host www.baidu.com left intact

三次握手:A主机与B主机
1.A发出连接请求
2.B确认A的请求,并向A发出确认请求
3.A确认B的确认请求,连接建立。
如果只有前两个步骤,A没有确认B的确认请求,可能会出现,当A的请求发生网络延迟,A连接失败,此时B确认请求后等待A的数据,而现在的A没有连接B,导致B的资源浪费 。

四次挥手:A主机与B服务器
1.A发起关闭连接
2.B确认关闭连接,此时处于半关闭状态,也就是A不能给B发送报文,B可以给A发送报文。
3.B等待当前报文传输结束,B发起关闭连接
4.A确认关闭连接。
A确认关闭连接后,有2MSL等待时间,用于清除网络中所有旧连接报文段,在新连接中不会出现旧连接的请求报文。

五层经典模型:
物理层:定义物理设备之间如何传输数据
数据链路层:在通信的实体间建立数据链路链接
网络层:为数据在节点之间的传输创建逻辑链路
传输层:向用户提供可靠的端到端的服务,传输层通过封装向高层屏蔽了下层数据通信的细节
应用层:为应用软件提供了很多服务,构建于tcp协议之上,屏蔽了网络传输相关的细节

HTTP协议是基于TCP/IP协议之上的应用层协议。

  • 基于请求-响应模式
  • 无状态保存【Cookie】
  • 无连接

HTTP请求方法:
GET、POST、HEAD、PUT、DELETE、TRACE、OPTIONS、CONNECT

HTTP状态码

  • 1xx消息——请求已被服务器接收,继续处理
  • 2xx成功——请求已成功被服务器接收、理解、并接受
  • 3xx重定向——需要后续操作才能完成这一请求【重定向】
  • 4xx请求错误——请求含有词法错误或者无法被执行【服务器无法处理请求】
  • 5xx服务器错误——服务器在处理某个正确请求时发生错误【服务器处理请求出错】

请求报文:请求行、请求头部、空行、请求数据
响应报文:状态行、响应头部、空行、响应数据

扫描二维码关注公众号,回复: 11622573 查看本文章

url基本元素

  • 传送协议。
  • 层级URL标记符号(为[//],固定不变)
  • 访问资源需要的凭证信息(可省略)
  • 服务器。(通常为域名,有时为IP地址)
  • 端口号。(以数字方式表示,若为HTTP的默认值“:80”可省略)
  • 路径。(以“/”字符区别路径中的每一个目录名称)
  • 查询。(GET模式的窗体参数,以“?”字符为起点,每个参数以“&”隔开,再以“=”分开参数名称与数据,通常以UTF8的URL编码,避开字符冲突的问题)
  • 片段。以“#”字符为起点

http请求体body的集中数据格式

  • multipart/form-data
  • application/x-www-form-urlencoded
  • raw 可以上传任意格式的文本
  • binary:上传二进制文件

接口分析工具–辅助

  • 基础款:开发者工具
    (1)XHR–查看调用模块【后台】
    (2)copy as cURL–可以使用curl命令,查看请求响应相关数据,修改请求相关参数
    (3)har格式–可自动生成接口测试用例
    (4)nc探测工具
  • 代理款:Charles
    (1)限速–Throttle Setting
    (2)断点–Breakpoint Setting
    (3)mock-map remote远程文件:模拟跳转预发布的网站
    (4)mock-map local本地文件:使用本地文件替换线上关键资源
    (5)mock-rewrite挡板:基于原有数据做修改–修改请求、响应等
  • 监听分析:tcpdump+wireshark【重】+har提取
  • 转发分析:修改host域名+反向代理转发

RestAssured简单介绍

RestAssured优势:

  • 简约的接口测试DSl
  • 支持xml、json的结构化解析
  • 支持xpath jsonpath、gpath等多种解析方式
  • 对Spring的支持比较全面

RestAssured基本用法:

  • 创建maven项目,导入RestAssured、Junit依赖包;
  • given()、when()、then() 模式测试语句
  • 基础的断言语句
    1. 代理语句,在charles中查看请求【https证书注意】
    2. 对比实际正常的请求
    3. json断言–查看github开源相关介绍,以及导入包中class方法【状态码、body对比】

接口测试封装思想

  • 配置封装:根据配置文件获取初始配置与依赖
  • 接口封装:封装接口调用进行抽象封装
  • 业务流程:数据初始化;业务用例设计,含有多个api形成的流程定义,不要再包含任何接口实现细节;断言
  • 封装范围:(1)测试数据的数据驱动;(2)测试步骤的数据驱动
  • 封装流程:(1)定义业务模型配置;(2)实现框架封装;(3)PO结合。

测试框架

  1. Api对象:完成对接口的封装
  • 架构设计:多协议支持,http、tcp、thrift等,需要不同的底层引擎;保证用例与协议无关,基于接口或者抽象实现
  • 实现
    code方式:Api输出=业务功能(输入)
    配置文件方式:yaml格式、json格式
    基于数据自动生成:swagger、har、log
  1. 接口测试框架:完成对api的驱动
  2. 配置模块:完成配置文件的读取
  3. 数据封装:数据构造与测试用例的数据封装
  • 架构设计:可以实现复杂的结构化数据;用明确定义的POJO或者指出复杂结构的HashMap代表数据
  • 实现:硬编码文件;POJO类:自己实现、从研发代码中提取、使用jackson库转化;模板文件:freemaker、mustache、jsonpath
  1. Utils:其他功能封装,改进原生框架不足,比如templates设计模型
  2. 测试用例:调用Page/API对象实现业务并断言

多环境支持

  • 联调环境
  • 测试环境
  • 预发布环境
  • 线上环境:url改成ip、host改成真实的id

数据库校验层

  • 注意数据库只适用于集成测试环节,不适用于线上环境,对于测试套件的组成需要规划
  • 数据库数据隐秘,可以做成接口访问形式,以json形式获取数据库数据
  • mysql、Oracle等数据库还不支持接口访问数据,需要自己做一个引擎访问。

account通用系统接口自动化实战

需求须知:剥离Account系统,将最外层Mapi层暴露出来做通用API设计,目的是方便快速制作产品。主要模块是:充值、提现、资金明细、明细日志、提现银行卡管理、银行地区等基础信息管理

接口类型:POST、application/json

自动化框架选取:Junit5 + RestAssured

设计用例组织结构:配置模块【初始通用配置】、接口框架封装【驱动Api】、APi模块【接口封装】、TestCase模块【实现业务并断言&数据清理要记得】、数据驱动【数据构造&测试用例】、Utils【其他功能封装】

用例组织结构
Config 配置模块
FrameWork 接口框架封装
PO模块:deposit/withdraw
API模块:返回Response
deposit
充值申请接口accountDepositApply
充值通道查询accountDepositChannel
当前用户账户查询
资金明细accountLog
withdraw
提现通道查询
提现申请
提现银行卡管理
TestCase模块
调用Api完成业务流程
断言
清理数据
数据驱动:json格式文件
Utils

Config配置:
单例模式

public class Config {
    public String host = "https://***";
    public String token;

    static Config config;
    
    public static Config getInstance(){
        if(config == null){
            config = new Config();
            config.token = config.getToken();
        }
        return config;
    }

    public String getToken(){
        token = given()
                .queryParam("",***)
                .get("https://***")
                .then().log().all()
                .statusCode(200)
                .extract().path("access_token");
        return token;
    }

}

FrameWork 接口框架封装
ApiObjectMethodModel

public class ApiObjectMethodModel {
    private HashMap<String, Object> params;
    public HashMap<String, Object> query;
    public HashMap<String, Object> header;
    public HashMap<String, Object> postBody;
    public String postBodyRaw;
    public String method = "get";
    public String url = "";

    public Response run() {
        RequestSpecification request = given();
        request.queryParam("access_token", Config.getInstance().token);

        if (query != null) {
            query.entrySet().forEach(entry -> {
                request.queryParam(entry.getKey(), repalce(entry.getValue().toString()));
            });
        }

        if (header != null) {
            query.entrySet().forEach(entry -> {
                request.header(entry.getKey(), repalce(entry.getValue().toString()));
            });
        }

        if (postBody != null) {
            //todo: replace hashmap
            request.body(postBody);
        }
        if (postBodyRaw != null) {
            request.body(repalce(postBodyRaw));
        }

        return request
                .when().log().all().request(method, url)
                .then().log().all().extract().response();
    }

    public String repalce(String raw){
        for (Map.Entry<String, Object> kv : params.entrySet()) {
            String matcher = "${" + kv.getKey() + "}";
            if (raw.contains(matcher)) {
                System.out.println(kv);
                raw = raw.replace(matcher, kv.getValue().toString());
            }
        }
        return raw;
    }

    public Response run(HashMap<String, Object> params) {
        this.params=params;
        return run();
    }
}

ApiObjectModel

public class ApiObjectModel {

    public HashMap<String, ApiObjectMethodModel> methods = new HashMap<>();

    public ApiObjectMethodModel getMethod(String method) {
        return methods.get(method);
    }

    public Response run(String method) {
        return getMethod(method).run();
    }

    public Response run(String method, HashMap<String, Object> params) {
        return getMethod(method).run(params);
    }
}

API模块

加解密:

相关模块:

数据驱动:Template+Json+csv

Template模板技术:拼装body内容

public class Template {
    public String template(HashMap<String,Object> data, String path)  {
        Writer writer = new StringWriter();
        new DefaultMustacheFactory().compile(path).execute(writer,data);
        return writer.toString();
    }
}

json文件

{
  "card_no","{{card_no}}";
  
}

在Api中拼装

public Response createCard(String data){
        return
         given().log().all()
                 .queryParam("access_token",Config.getInstance().token)
                 .contentType("application/json")
                 .body(data)
                 .when()
                 .post("https://***")
                 .then()
                 .statusCode(200)
                 .extract().response();
    }

     public Response createCard(String cardNo,String path){
         HashMap<String,Object> map = new HashMap<>();
         map.put("card_no",cardNo);
         map.put("",***);  //将json文件中需要变化的参数化
         Template template = new Template();
         return createCard(template.template(map,path));

     }

Junit5 CSVFileSource 参数化

    @ParameterizedTest
    @CsvFileSource(resources = "1.csv")
    public void createCard(String cardNo){
       //todo:调用Api中createCard方法
    }

TestCase

业务实现
断言
数据清理

关于RestAssured

静态导入方法:

io.restassured.RestAssured.*
io.restassured.matcher.RestAssuredMatchers.*
org.hamcrest.Matchers.*

猜你喜欢

转载自blog.csdn.net/LittleGirl_orBoy/article/details/104806311