HTTP协议
接口知识:
HTTP协议
对应上述请求过程:
- 浏览器向 DNS 服务器请求解析该 URL 中的域名所对应的 IP 地址;
- 解析出 IP 地址后,根据该 IP 地址和默认端口 80,和服务器建立TCP连接;
- 浏览器发出读取文件(URL 中域名后面部分对应的文件)的HTTP 请求,该请求报文作为 TCP 三次握手的第三个报文的数据发送给服务器;
- 服务器对浏览器请求作出响应,并把对应的 html 文本发送给浏览器;
- 释放 TCP连接;
- 浏览器将该 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&tpl=mn&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>©2017 Baidu <a href=http://www.baidu.com/duty/>使用百度前必读</a> <a href=http://jianyi.baidu.com/ class=cp-feedback>意见反馈</a> 京ICP证030173号 <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服务器错误——服务器在处理某个正确请求时发生错误【服务器处理请求出错】
请求报文:请求行、请求头部、空行、请求数据
响应报文:状态行、响应头部、空行、响应数据
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() 模式测试语句
- 基础的断言语句
- 代理语句,在charles中查看请求【https证书注意】
- 对比实际正常的请求
- json断言–查看github开源相关介绍,以及导入包中class方法【状态码、body对比】
接口测试封装思想
- 配置封装:根据配置文件获取初始配置与依赖
- 接口封装:封装接口调用进行抽象封装
- 业务流程:数据初始化;业务用例设计,含有多个api形成的流程定义,不要再包含任何接口实现细节;断言
- 封装范围:(1)测试数据的数据驱动;(2)测试步骤的数据驱动
- 封装流程:(1)定义业务模型配置;(2)实现框架封装;(3)PO结合。
测试框架
- Api对象:完成对接口的封装
- 架构设计:多协议支持,http、tcp、thrift等,需要不同的底层引擎;保证用例与协议无关,基于接口或者抽象实现
- 实现
code方式:Api输出=业务功能(输入)
配置文件方式:yaml格式、json格式
基于数据自动生成:swagger、har、log
- 接口测试框架:完成对api的驱动
- 配置模块:完成配置文件的读取
- 数据封装:数据构造与测试用例的数据封装
- 架构设计:可以实现复杂的结构化数据;用明确定义的POJO或者指出复杂结构的HashMap代表数据
- 实现:硬编码文件;POJO类:自己实现、从研发代码中提取、使用jackson库转化;模板文件:freemaker、mustache、jsonpath
- Utils:其他功能封装,改进原生框架不足,比如templates设计模型
- 测试用例:调用Page/API对象实现业务并断言
多环境支持
- 联调环境
- 测试环境
- 预发布环境
- 线上环境:url改成ip、host改成真实的id
数据库校验层
- 注意数据库只适用于集成测试环节,不适用于线上环境,对于测试套件的组成需要规划
- 数据库数据隐秘,可以做成接口访问形式,以json形式获取数据库数据
- mysql、Oracle等数据库还不支持接口访问数据,需要自己做一个引擎访问。
account通用系统接口自动化实战
需求须知:剥离Account系统,将最外层Mapi层暴露出来做通用API设计,目的是方便快速制作产品。主要模块是:充值、提现、资金明细、明细日志、提现银行卡管理、银行地区等基础信息管理
接口类型:POST、application/json
自动化框架选取:Junit5 + RestAssured
设计用例组织结构:配置模块【初始通用配置】、接口框架封装【驱动Api】、APi模块【接口封装】、TestCase模块【实现业务并断言&数据清理要记得】、数据驱动【数据构造&测试用例】、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.*