软件测试之——接口测试

接口

接口其实就是在服务器端定义的一堆函数或者方法的集合,接口调试其实就是在调用这堆函数或者方法。

由于接口是在两台电脑之间远程去调用这些方法,所以不能直接通过函数名来进行调用。不同电脑之间如果进行通讯,必须通过特定的网络协议来实现,所以接口调用必须借助于网络协议来进行调用。

网络协议

网络协议是通信计算机双方必须共同遵守的一组约定,如怎么样建立连接,怎么样互相识别等。只有遵守这个约定,计算机之间才能相互通信交流。它最终体现为在网络上传输数据包的格式

协议往往分成几个层次进行定义,分层定义时为了使某一层协议的改变不影响其他层次的协议

tcp/ip四层协议

osi模型

tcp/ip模型

第七层

应用层

application

应用层

第六层

表示层

presentation

第五层

会话层

session

第四层

传输层

transport

传输层

第三层

网络层

network

Internet层

第二层

数据链路层

data link

数据链路层

第一层

物理层

physical

应用层:Telnet/ftp/http/smtp

传输层:UDP/TCP

网络层:ip、ICMP、ARP、RARD

数据链路层:网络接口及硬件层

http协议

http协议,称为超文本传输协议,是互联网上应用最为广泛的一种网络协议。是基于tcp/ip模型的应用层协议

超文本:不但可以传输文本数据,还可以传输音频、视频、超链接、图片等内容

url组成

http://www.baidu.com:80/adv_search?order=false&kw=py#tag

  • 协议名称: http
  • 请求域名:www.baidu.com
  • 端口号:80
  • 资源路径:adv_search
  • 请求参数:order=false&kw=py
  • 页面锚点:#tag

请求模型

http是由请求和响应构成,是个标准的客户端服务器模型

请求过程

一次http操作称为一个是事务

  1. 浏览器输入
  2. dns域名解析:域名与ip映射
  1. 建立tcp连接
  2. 发送http request:请求信息
  1. web服务器接收请求
  2. 应用服务器处理业务逻辑
  1. 关闭tcp连接:请求响应完成。如果浏览器在其头部信息中加入了connection:keep-alive,则tcp连接仍然保持打开状态
  2. 浏览器渲染响应内容

特性

  1. 无连接性:无连接的含义是限制每次连接只处理一个请求,服务器处理完客户的请求,并受到客户的应答后,即断开连接,采用这种凡是可以节省传输时间。Keep-Alive 功能是客户端的连接特性持续有效,避免建立或者重新建立连接,当客户端发送另外一个请求时,就是用这条已经建立的连接

请求类型

  • get:从服务器端获取资源或数据
  • post:向服务端提交数据
  • options:返回服务器针对特定资源所支持的http请求
  • head:获取与get请求一致的响应头
  • put:用来更新一个已有的实体。通过把已经存在的资源的id和新的实体用put请求上传到服务器来更新资源
  • delete:从服务器上删除资源,需要把要删除的资源的id上传给服务器
  • trace:回显服务器收到的请求,主要用于测试或诊断

get和post的区别

  • get请求一般用于向服务器请求获取一个资源,没有副作用,一般在客户端做缓存。post请求一般用于向服务器提交数据并让其完成一件事,所以这个操作不会在客户端做缓存
  • get请求发送数据的时候,一般会将请求数据放在url字符串中发送给服务器端,所以从安全性角度来看相对没有post请求安全性高,所以get请求一般不会用于比较隐私数据的传输。而post请求是将请求数据放在请求体body里面,所以一般用于表单数据、登录数据等数据的传输。

请求的组成部分

  • URL地址
  • 请求参数(可选)
  • 请求头
  • 请求体(仅限post请求)

响应的组成部分

  • 响应状态码
  • 响应内容

响应内容一般有两种格式:

  • json格式:直接会用request库的responce对象自身的json方法,将响应内容转化为json对象后再提取
  • HTML格式:
    • 利用lxml库以xpath方式提取
    • 用正则表达式提取

状态码

  • 1**:临时响应并需要请求者继续执行操作
  • 2**:请求成功。操作被成功接收并处理
  • 3**:重定向代码,用于已经移动的文件并且在头信息中指定新的地址信息
  • 4**:客户端错误,请求包含语法错误或者无法完成请求
  • 5**:服务器错误,服务器在处理请求的过程中发生错误

Chrome抓包

network操作

  • preserve log:打钩,就可以保留以前的请求的network日志
  • disable cache: 打钩,可以禁用页面缓存
  • fiter: 可以搜索请求
  • XHR: 获得异步请求
  • DOC:HTML页面
  • WS:websocket请求
  • ALL:所有的请求

请求头(Headers):

  • user-agent:请求的客户端
  • url
  • 请求类型
  • 请求数据类型
  • 请求参数

返回(response)

fildder抓包

  1. 通过fildder,能够让我们获取请求和响应的内容
  2. 篡改请求或响应的内容

fildder操作

请求与响应在Inspectors下面看

filler监听的端口

filddler的监听端口是8888

  1. 电脑端设置

在浏览器的“设置”-》“高级”-》“系统”-》“打开您计算机的代理设置”

  1. 手机端设置

fildder->tool->options->HTTPS->Decrept HTTPs traffic勾选->Ignore server certificate errors(unsafe)

fildder->tool->options->connection->allow remote computer to connect

手机->设置-》wifi->高级选项-》设置代理服务器的地址ip和端口(8888)

手机-》下载证书:浏览器访问:http://ip:8888->点击FildderRoot certificate下载证书并安装

过滤指定的地址

fildder软件中的“Filters”->勾选 use filters->"Show only the following Hosts"的下拉选项 ->填写指定的域名:端口。各个地址之间以分号;分割 ->点击actions下的悬浮菜单run Filterset now

接口测试

概念

  • 接口测试时测试系统组件间接口的一种测试
  • 接口测试主要用于检测外部系统与系统之间以及内部各个子系统之间的交互点。测试的重点是要检查数据的交换、传递、控制管理过程,以及系统间的相互逻辑依赖关系等
  • 系统内部的接口调用相当于函数调用,系统外部的接口打不扥都是基于http协议的调用

优势

相比较于UI测试,能更早的发现问题,能发现更底层的问题,发现和解决的效率更高

接口设计文档

接口设计文档主要包含以下几个部分:

  • 接口说明
  • 请求方式
  • 请求URL
  • 请求参数
  • 返回数据
  • 返回示例

接口测试用例设计

接口测试的原理就是模拟客户端向服务器发送请求,服务器接收请求报文后对响应的报文做处理并向客户端返回应答,客户端接收应答的过程。

接口测试采用的方法其实与黑盒测试是一致的,甚至可以把接口测试理解为没有界面的功能测试。只不过接口测试的关注点在请求和响应上。另外,还包括接口的安全、性能等。常用的用例设计有等价类、边界值法

一般测试用例的实际要从单接口参数的校验到整个业务功能点的验证,还可以验证一些安全性和异常情况。

接口测试的用例一般包含如下字段:

  • 用例id
  • 所属模块
  • 测试点
  • 前置条件
  • 接口url
  • 依赖接口
  • header
  • method
  • body/param
  • 预期结果
  • 接口验证点:code、msg的值
  • 实际结果
  • 测试结果
  • 执行时间
  • 执行人

接口测试要点

  1. 检查接口返回的数据是否与预期结果一致
  2. 接口测试结果检查通常需要通过SQL语句从数据库内进行查询确认
  1. 检查接口的容错性,假如传递数据的类型错误时是否可以处理
  2. 接口参数的边界值。例如:传递的参数足够大或为负数时,接口是否可以正常处理
  1. 接口的性能,http请求接口大多与后端执行SQL语句性能、算法等比较相关
  2. 接口的安全性,外部调用的接口尤为重要,一般大多数接口都要进行鉴权验证

正则表达式

常用符号

  • | 表示选择其中一个进行匹配 a|b a或者b
  • . 点号,匹配出去\n之外的任意字符
  • ^ 匹配字符串起始部分
  • $ 匹配字符串终止符
  • * 匹配0或者多次左端出现的正则表达式 [0-9]*
  • + 匹配1或多次左端出现的表达式
  • ?匹配0次或者1次左端出现的表达式
  • {N} 匹配n次左端出现的正则表达式
  • {M,N} 匹配m到n次左端出现的表达式
  • [...] 匹配来自括号中字符集的任意一个
  • [a-b] 匹配a到b之间的任何一个字符
  • [^...] 不匹配此字符集中的任何一个字符
  • () 对正则表达式进行分组或者匹配子组 ([0-9]{3})?表示匹配三个数字出现一次或者不出现的情况

特殊字符

  • \d (\D) \d匹配任何一个十进制数字,\D不匹配任何数字
  • \w(\W) \w匹配任何一个字母字符 ,\W不匹配任何字母字符
  • \s(\S) \s匹配任何空格字符,\S不配任何空格字符
  • \b(\B) \b匹配任何单词边界,\B不匹配任何单词边界 \bthe 任何一the开始的字符串
  • \N 匹配以保存的子组N price:\N 匹配price:后面加以保存的子组N的形式
  • \c 仅按照c的字面意思进行匹配 \* 表示匹配*
  • \A(\Z) \A匹配字符串的开始,\Z匹配字符串的结尾

例子

常用符号举例

import re

log = '''
    now: 2021-09-09     22:32:58 id:  ST0013 status:    nok
    now: 2021-09-10 12:32:48 id: DAMX002 status:    good
    now: 2021-09-19 20:32:28 id: SIMFDHGS0 status:    well
    now: 2021-09-29  22:20:18 id: 04 status:    unknowm
    
'''
# ()中代表要取出的字符串
findall = re.findall('now:\s(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2}:\d{2})', log)
# [('2021-09-09', '22:32:58'), ('2021-09-10', '12:32:48'), ('2021-09-19', '20:32:28'), ('2021-09-29', '22:20:18')]
print(findall)

# ()中代表要取出的字符串
ids = re.findall('id:\s+(\w*\d+)', log)
# ['ST0013', 'DAMX002', 'SIMFDHGS0', '04']
print(ids)

status = re.findall('status:\s+(\w+)', log)
# ['nok', 'good', 'well', 'unknowm']
print(status)

.*?的用法,主要用于提取指定区域的所有内容

content = '''
	[
    {"Sid":1,"name":"消化"},
    {"Sid":1,"name":"肠胃"},
    {"Sid":1,"name":"不良"},
    {"Sid":1,"name":"很瘦"},
    {"Sid":1,"name":"很好"},
    {"Sid":1,"name":"健康"},
    ]
'''
ss = re.findall('"name":"(.*?)"', content)
# ['消化', '肠胃', '不良', '很瘦', '很好', '健康']
print(ss)

如果字符串中包含\n,或者字符串是html格式的,要加re.S

import re

log = '''
    now: 2021-09-09     22:32:58 id:  ST0013 status:    nok
    now: 2021-09-10 12:32:48 id: DAMX002 status:    good
    now: 2021-09-19 20:32:28 id: SIMFDHGS0 status:    well
    now: 2021-09-29  22:20:18 id: 04 status:    unknowm
    
'''
# ()中代表要取出的字符串
findall = re.findall('now:\s(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2}:\d{2})', log)
# [('2021-09-09', '22:32:58'), ('2021-09-10', '12:32:48'), ('2021-09-19', '20:32:28'), ('2021-09-29', '22:20:18')]
print(findall)

# ()中代表要取出的字符串
ids = re.findall('id:\s+(\w*\d+)', log)
# ['ST0013', 'DAMX002', 'SIMFDHGS0', '04']
print(ids)

status = re.findall('status:\s+(\w+)', log)
# ['nok', 'good', 'well', 'unknowm']
print(status)

content = '''
	[
    {"Sid":1,"name":"消化\n"},
    {"Sid":1,"name":"肠胃\n"},
    {"Sid":1,"name":"不良\n"},
    {"Sid":1,"name":"很瘦\n"},
    {"Sid":1,"name":"很好\n"},
    {"Sid":1,"name":"健康\n"},
    ]
'''
# ss = re.findall('"name":"(.*?)"', content)
# 字符串中包含了\n,或者字符串是html文档格式的,要加参数re.S
ss = re.findall('"name":"(.*?)"', content, re.S)
# ['消化', '肠胃', '不良', '很瘦', '很好', '健康']
print(ss)

用python完成接口测试

Requests库,使用python语言编写,基于urllib,采用apache2 Licensed开源协议的http库。

  1. 安装Request库 pip install -i https://pypi.douban.com/simple requests
  2. 首先导入Request模块 import requests

请求

get请求

import requests


def test1():
    resp = requests.get("http://www.baidu.com")
    # 响应的状态码
    print(resp.status_code)
    # 响应对象的文本信息
    print(resp.text)


def test2():
    resp = requests.get("http://www.baidu.com/s?wd=python")
    # 响应的状态码
    print(resp.status_code)
    # 响应对象的文本信息
    print(resp.text)
    # 获取响应对象所对应的请求地址
    print(resp.url)


def test3():
    data = {
        'wd': 'python'
    }
    resp = requests.get("http://www.baidu.com/s", params=data)
    # 响应的状态码
    print(resp.status_code)
    # 响应对象的文本信息
    print(resp.text)
    # 获取响应对象所对应的请求地址
    print(resp.url)


if __name__ == '__main__':
    test3()

post请求

    def test_post1():
        data = {
            'username': 'xiaohua'
        }
        # 以formdata的形式传递参数: requests.post('url', data=data)
        # resp = requests.post('http://localhost:8080/login', data=data)
        # 以json的形式传递参数:requests.post('url', json=data)
        resp = requests.post('http://localhost:8080/login', json=data)
        print(resp.status_code)
        print(resp.text)

添加或修改header信息

def test_header():
    headers = {
        "Cookie": "fkjadhgquy41dsfbad91lksdf",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36",
    }
    resp = requests.get('http://www.httpbin.org/headers', headers=headers)
    print(resp)
    print(resp.text)

使用session对象

def test_session():
    # 创建一个session对象,保存客户端的会话信息
    sess = requests.session()
    data = {
        'username': 'xiaohua',
        'password': '123456'
    }
    sess.post('http://localhost:8080/login', data=data)
    resp2 = sess.get('http://localost:8080/getUserName')
    print(resp2.text)

文件上传

上传形式为from-data,以key为file上传文件即可

def test_upload():
    file = {
        # 上传test.txt文件,rb二进制
        'file': open('test.txt', 'rb')
    }
    resp = requests.post('http://127.0.0.1:8080/upload', files=file)
    print(resp.status_code)
    print(resp.text) 

上传文件带参数

def test_upload2():
    '''
    上传文件带参数
    :return:
    '''
    data = {
        # 这里面传除文件名以外的其他参数
        'batchname': 'Gb123438'
    }
    files = {
        'batchfile': open('a.xls', 'rb')
    }
    resp = requests.post('http://127.0.0.1:8080/upload', files=files, data=data)
    print(resp.status_code)
    print(resp.text)

加密参数的请求

def test_sign():
    uid, name, password, salt = '3', 'qcj', '123456', 'LDJSIFN23J2df'
    import hashlib
    md_1 = hashlib.md5()
    # 以指定格式保存需要加密的字段
    md_1.update(('{}-{}-{}-{}'.format(uid, name, password, salt)).encode('utf-8'))
    # 对md_1中保存的字段进行MD5加密算法
    sign = md_1.hexdigest()
    print(sign)
    data = {
        'uid': 3,
        'sign': sign
    }
    resp = requests.post('http://127.0.0.1:8080/general/userinfo_sign', json=data)
    print(resp.text)

接口参数关联

def test_relation():
    data = {
        'username': 'xiaohua',
        'password': '123456'
    }
    resp1 = requests.post('http://localhost:8080/login', data=data)
    # 转成json
    resp_json = resp1.json()
    # 拿到json中的token字段
    token_auth = resp_json['token']
    headers = {
        'auth-token': token_auth
    }
    resp2 = requests.get('http://localost:8080/getUserName', headers=headers)
    print(resp2.text)

响应

接口响应内容的形式主要有:json、HTML源码、程序自定义的字符串

处理方式:正则表达式解析任意形式、lxml库解析HTML、json解析

lxml

  1. 安装lxml : pip install -i https://pypi.douban.com/simple lxml
  2. 导入 from lxml import etree
import requests
from lxml import etree


def verfifyLogin():
    headers = {
        'cookie': 'hfrgqlkhgksaudhafehh143tkdbvf191hfkt3hy'
    }
    resp = requests.get("http://xx.xx.com/xx/xx/xx.html", headers=headers)
    resp.encoding = 'utf-8'
    # 将返回内容转成HTML格式
    htmlresp = etree.HTML(resp)
    # 获取a标签的属性href的值为javascript:loginOff() 的元素上显示文本信息
    result = htmlresp.xpath("//a[@href='javascript:loginOff()']/text()")
    print(result)

json

  • 通过resp.json()将返回转换为json;
  • resp_json['键'] 取对应键的值

apifox完成接口测试

apifox 是接口管理、开发、测试全流程集成工具,通过一套系统、一份数据,解决多个系统之间的数据同步问题。只要定义好接口文档,接口调试、数据Mock、接口测试就可以直接使用,无需再次定义。接口文档和接口开发调试使用同一个工具,接口调试完成后即可保证和接口文档完全一致。高效、及时、准确

PostMan+Swagger+Mock+JMeter=ApiFox

前置操作生成sign

// 使用crypto-js库
var cryptoJS = require("crypto-js");
// 要签名的字符串
var signstr = '1-xiaohua-26'
// 使用md5签名
var sign = crpytoJS.MD5(signstr).toString();
// 设置到环境变量sign中。后面其他接口可以直接使用这个变量,以{{sign}}使用
pm.environment.set("sign",sign)
console.log(sign)

后置操作

  • 可以将登陆接口获取的token,在后置操作中配置进变量中,方便其他接口使用。
  • 也可以在后置操作中添加断言

上传文件

body中选择form-data。字段类型选择file

测试用例管理

测试用例管理,是将多个接口有序地组合在一起运行,用来测试一个完整业务流程

在“测试管理”-》“测试用例”-》新建测试用例-》支持从http接口导入,或者http接口用例导入

  • 支持上传数据驱动文件,数据驱动文件中的变量名,就作为请求的入参。{{数据驱动的变量名}}

生成测试报告

  1. 导出测试用例:apifox cli格式。就会生成xxxx.json文件
  2. 通过命令行运行:apifox run xxxx.json -d data.csv -n 2 -r html
    1. -d :测试用到的数据文件
    2. -n:循环次数.行数
    1. -r:在当前路径生成测试报告,并指定报告格式

如果对python自动化测试、web自动化、接口自动化、移动端自动化、大型互联网架构技术、面试经验交流等等感兴趣的老铁们,可以关注我。我会在公众号(程序员阿沐)/群里(810119819)不定期的发放免费的资料链接,这些资料都是从各个技术网站搜集、整理出来的,如果你有好的学习资料可以私聊发我,我会注明出处之后分享给大家。欢迎分享,欢迎评论,欢迎转发。需要资料的同学可以关注我获取资料链接。


版权声明:本文为m0_61655732原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。