一、接口测试理论
1. 定义
接口的本质
它其实就是一种契约,遵循这样一种形式:在开发前期,我们约定接口会接收什么数据;在处理完成后它又会返回什么数据。
什么是接口?一个系统或两个不同的系统中中不同的两个功能,他们之间相互连接的部分就是接口
接口测试的定义
接口测试其实就是模拟客户端,通过接口通信来检测被测接口的正确性和兼容性接口测试是测试系统组件间接口的一种测试,主要检测系统内部组件与系统或外部间的交互点
接口测试的基本步骤
- 准备测试数据
- 通过接口测试工具,发起对被测接口的请求
- 验证返回结果的响应
前后端分离
前端与后端协商好接口定义方式,由后端提供接口功能,前端调用接口功能,以实现预期的功能,优势如下:
- 后端无需精通前端技术,只需专注API数据处理即可
- 前端无需关心后端功能如何实现,只需专注页面设计
- 可以扩大接口的应用范围,比如web接口应用到APP中
流程图
2. 接口测试的分类
按照使用场景划分:
- 内部接口:系统自身的接口,如淘宝的登录,投资理财产品等
- 外部接口:系统提供给外部使用的接口,如微信支付,支付宝支付等
3. 接口测试的目的
- 符合尽早测试的原则,
bug
发现越早,修复成本越低 - 可以覆盖更多测试场景(绕开了前端页面的限制)
- 后期可以扩展成接口自动化、性能、安全等测试
4. 测试金字塔
各层测试占比:
unit(70%
: 单元测试Api(20%)
:接口测试UI(10%)
:用户界面测试
5. 接口术语
5.1 接口分类
HTTP
接口:基于HTTP协议,常用方法get, post
,常用数据格式是json
webservice
接口:走soap
协议,常用数据格式是xml
5.2 传输数据的格式
json
{"name":"zs"}
xml
<note>
<to>George</to>
<from>John</from>
<heading>Reminder</heading>
<body>Don't forget the meeting!</body>
</note>
5.3 cookie
cookie
是储存在客户端的一小段文本,可以跟随请求一起发送至服务器,目的为了保持客户端的状态(因为HTTP
是一种无状态协议)
5.4 session
session
是一种存放在服务器端的用来保存用户数据一种保持状态
的方案
5.5 token
token
其实就是一个 令牌,由服务器生成,返回给客户端,作为客户端请求的一个标识
6. HTTP接口的请求方法
get
一般用于向指定URL
请求资源,比如说:查询订单,查询个人信息等
post
一般用于向指定URL
提交数据,比如注册,登录,新增等等
put
一般用于向指定URL
提交最新的数据,比如修改信息,修改密码等等
delete
一般用于删除特定资源,比如删除订单,删除评论等等
7. 接口测试的范围
- 功能测试(参数的长度,类型,是否必填,唯一性等等)
- 安全测试(是否能篡改数据,数据是否加密等等)
- 逻辑测试(验证先后顺序)
- 性能测试(cpu,内存,吞吐量,响应时间,并发数… …)
-
8. 接口测试的流程
需求分析
- 熟悉接口文档
- 编写测试要点(非必须)
- 编写测试用例
- 执行测试用例
- 编写自动化脚本(如果做了接口自动化的情况下)
- 编写测试报告
9. 接口测试的工具
- jmeter:主流性能测试工具,也可以用来做接口测试
- postman:界面美观,功能齐全的接口测试工具
- requests:
python
中的一个用来做接口测试的模块,可以实现接口自动化测试 - soapui:测试
webservice
接口的工具 - robotframework:基于
Python
的一个关键字测试框架10. 接口测试用例设计
二、Requests
1. 安装
pip install requests
2. get请求
向目标URL
发送get
请求,接收响应get
方法参数说明如下:
url
:必填,指定请求的目标网址params
:指定请求参数,字典类型
百度搜索实例:
import requests
# 发送get请求
r = requests.get('https://www.baidu.com', params={'wd':'Python'})
print(r.status_code) # 查看响应码
print(r.text) # 查看响应文本
print(r.url) # 查看请求地址
print(r.cookies) # 查看请求中的cookie
print(r.headers) # 查看请求头部信息
# 将响应数据写入文件
with open('baidu.html', 'w', encoding='utf-8') as f:
f.write(r.text)
r.encoding = r.apparent_encoding # 自动解码响应文本(乱码时添加)
3. post请求
向目标URL
发送post
请求,接收响应post
请求参数说明如下:
url
:指定请求的目标网址data
:指定请求的参数,字典类型json
:指定请求的参数,json类型
注意:
- 如果请求头部中出现了
content-type
为form
,则请求参数以data
传入,即传入字典类型 - 如果请求头部中出现了
content-type
为json
,则请求参数以json
传入,即传入json
类型
ecshop登录实例:
datas = {"username":"liyihang", "password":"tashi123", "act":"act_login"}
r = requests.post('http://localhost:82/ecshop/user.php', data=datas)
print(r.status_code)
print(r.text)
result = r.text
if result.find('欢迎您回来')!= -1:
print('登录成功')
else:
print('登录失败')
post传入字典:
data = {"name":"zs", "age":18}
r =requests.post('http://httpbin.org/post', data=data) # 传入字典类型数据
{
"form": { "age": "18", "name": "zs"}, # 字典数据传在这儿
"json": null, # json数据为空
}
post传入json:
r =requests.post('http://httpbin.org/post', json=json.dumps(data)) # 传入json数据
注意:
- https://requests.readthedocs.io/zh_CN/latest/user/quickstart.html#post
# 2.4以后 会自动将dict转换成json 无需手动处理 r = requests.post("http://httpbin.org/post", json={"name":"zs"} )
{
"form": {}, # form为空
"json": "{\"name\": \"zs\", \"age\": 18}", # json数据传在这儿
}
4. 保持会话
在项目中,很多操作都是需要先登录才能进行,所以要先发送登录请求并保持登录成功的状态,这样才能进行后续操作。在requests
中,可以使用session
来保持登录状态
s = requests.session() # 定义一个session
r1 = s.post('http://localhost:82/ecshop/user.php', data=datas) # 在session中发送登录请求
recharge = {"amount":100, "user_note":"20191502", "payment_id":2, "act":"act_account"}
r2 = s.post('http://localhost:82/ecshop/user.php', data=recharge) # 在session中发送充值请求
result2 = r2.text
if result2.find('您的充值金额为')!= -1: # 验证充值是否成功
print('充值成功')
else:
print('充值失败')
5. 自定义请求头
很多网站为了保证安全以及减轻服务器压力,都会设置反爬虫机制
,所谓反爬虫机制
就是指对于非浏览器
的请求拒绝处理,在这种情况下,就需要自定义headers
中的数据,伪装
成浏览器即可正常访问。
以访问 知乎发现页 为例:
import requests
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36"
} # 该参数值通过fiddler抓取获得
r = requests.get('https://www.zhihu.com/explore', headers=headers)
print(r.status_code)
print(r.text)
6. 关闭证书验证
在发送HTTPS
请求时,如果开启了Fiddler
,代码可能会出现requests.exceptions.SSLError: HTTPSConnectionPool
错误,如果出现这个错误,可以关闭请求中的证书验证规避:
r = requests.get('https://www.zhihu.com/explore', headers=agent, verify=False) # verify=False 关闭证书验证
关闭证书验证后,出现的用户警告,额可以通过urllib3
中disable_warnnings()
关闭
import urllib3
urllib3.disable_warnings() # 关闭警告
三、Postman
1. 下载安装
- 官网:https://www.getpostman.com
2. 新建目录
3. 发送get请求
4. 发送post请求
5. 添加断言
6. 关联token
目的:fecshop项目在后台查询订单时需要先登录,将登录后token关联至查询订单请求中,可以使用如下设置公共变量的方法
- 点击
postman
右上角小齿轮
添加一个环境 - 点击右上角下拉框切换到新建好的环境
在登录请求中的
tests
选项卡中增加提取token
并设置变量的JS
代码//将token提取出来并存到变量中 var value = postman.getResponseHeader("Access-Token"); //将上面的变量设置成环境变量 pm.environment.set("Access-Token",value);
在查询订单请求中使用刚刚设置好的变量
- 发送一次登录请求,点击右上角
眼睛
图标,查看环境变量是否已经获取到 - 发送查询订单请求,请求成功
四、项目实战
1. 新建项目结构
项 目
|----case
|----test_login.py
|----report
|----xxx-report.html
|----run.py
2. 编写测试脚本
写脚本要使用
unittest
单元测试框架
unittest
单元测试框架可以方便用例的管理与维护- 同时可以为测试报告提供数据
登录 脚本:
import unittest
import requests
class Login(unittest.TestCase):
def setUp(self):
self.url = "http://appapi.fecshop.com/v1/account/login"
self.login_data = {"username":"admin", "password":"admin123"}
def tearDown(self):
pass
def test_01_login_success(self):
"""用户名密码正确,登录成功"""
r = requests.post(self.url, data=self.login_data) # 发送登录请求
self.assertIn('success', r.text) # 断言success是否在响应文本中
if __name__=='__main__': # 程序入口
unittest.main()
查询订单 脚本
import json
import unittest
import requests
class QueryOrderList(unittest.TestCase): # 定义类继承unittest.TestCase
def setUp(self): # 初始化方法
url = 'http://appapi.fecshop.com/v1/account/login'
datas = {"username": "admin", "password": "admin123"}
r = requests.post(url, data=datas) # 发送登录请求
result = json.loads(r.text) # 保存响应数据并且转化为字典
token = result["access-token"] # 取出响应字典中token
self.header = {'Access-Token':token} # 构造一个包含token请求头
def test_01_query_success(self):
'''参数正确,查询订单列表成功'''
url = 'http://appapi.fecshop.com/v1/article/list'
# 发送查询订单列表请求,并且在该请求中带入构造好请求头
r = requests.get(url, params={"page":1, "password":"admin123"}, headers=self.header)
result = r.text # 保存查询订单列表的响应数据
message = result.find('success') # 在响应数据中查找 success字符
self.assertNotEqual(message, -1) # 断言是否出现success
if __name__=="__main__":
unittest.main()
3. 常见的断言方法
unittest
框架常用的断言如下:
序号 | 断言方法 | 断言描述 |
---|---|---|
1 | ``assertEqual(a,b) | 判断a是否与 b相等,不相等则``fail |
2 | ``assertNotEqual(a,b) | 判断a是否与 b不相等,相等则``fail |
3 | ``assertIn(a,b) | 判断a是否在 b中,不在则``fail |
4 | ``assertNotIn(a,b) | 判断a是否不在 b中,在则``fail |
5 | ``assertTrue(a) | 判断a是否为 True,如False则 fail |
6 | ``assertFalse(a) | 判断a是否为 False,如True则 fail |
4. 安装BeautifulReport
pip install beautifulreport
5. 编写一键执行脚本
import unittest
from BeautifulReport import BeautifulReport
import time
current_time = time.strftime("%Y-%m-%d-%H-%M-%S") # 2020-09-27-17-35-10
file_name = current_time + "-report.html" # 拼接报告文件名
if __name__ == '__main__':
# 从case目录下去查找所有以test开头的py文件中的所有测试用例
suite = unittest.defaultTestLoader.discover(".\case")
# 实例化BR
res = BeautifulReport(suite)
# 调用类中的生成测试报告的方法
res.report("Fecshop接口自动化测试", file_name, r".\report")
6.执行测试,生成报告
7. 新建配置目录
项 目
|----case
|----test_login.py
|----report
|----xxx-report.html
|----config # 配置文件目录
|----config.py # 配置文件
|----run.py
config.py
# 定义一些常用配置
HOST = 'http://appapi.fecshop.com/v1'
8. 新增测试数据目录
项 目
|----case
|----test_login.py
|----report
|----xxx-report.html
|----config
|----config.py
|----data # 测试数据目录
|----login_data.csv # 测试数据文件
|----run.py
9. 新增工具类目录
项 目
|----case
|----test_login.py
|----report
|----xxx-report.html
|----config
|----config.py
|----data
|----login_data.csv
|----utils
|----csv_reader.py # 测试数据读取
|----get_token.py # 登录并 获取token
|----run.py
csv_reader.py
import csv # 导入csv模块
def get_data(filename):
content = [] # 定义空列表
with open(filename, 'r') as f: # 打开文件
reader = csv.DictReader(f) # 实例化dictreader,得到一个reader对象
for i in reader: # 遍历reader
content.append(i) # 将读取的内容追加列表中
# 返回列表
return content
if __name__=='__main__': # 测试本文件
c = get_data('D:/201915/fecshop/data/login_data.csv')
print(c)
get_token.py
import sys
sys.path.append('..')
import requests
import json
from config.config import *
from utils.csv_reader import get_data
def get_token_for_header():
url = HOST + '/account/login'
data = get_data(DATA_PATH+'/login_data.csv')
r = requests.post(url, json.loads(data[0]['data']))
result = json.loads(r.text)
token = result['access-token']
header = {'Access-Token': token}
return header
10. 调试用例
test_login.py
import json
import os,sys
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
import unittest
import requests
from config.config import *
from utils.csv_reader import get_data
file_path = DATA_PATH + '/login_data.csv' # 组织登录数据文件存放路径
login_data = get_data(file_path) # 拿到登录数据
class Login(unittest.TestCase):
def setUp(self): # 初始化方法,在每个用例执行前都会执行
self.url = HOST + '/account/login'
# self.datas ={"username":"admin", "password":"admin123"}
def tearDown(self): # 结束方法,在每个用例结束后都会执行
pass
def test_01_login_success(self): # 用例名称必须以test开头,编号可以使用例按照编号顺序执行
'''用户名密码正确,登录成功'''
datas = login_data[0]['data'] # 从login_data中取出第一行数据中的data
r = requests.post(self.url, data=json.loads(datas)) # 发送登录请求,注意将str转成dict
result = r.text # 保存响应信息
status = result.find(login_data[0]['result']) # 在响应中查找success
self.assertNotEqual(status, -1) # assertNotEqual(a, b)断言a,b不相等
def test_02_login_fail(self):
'''用户名正确,密码错误,登录失败'''
data = login_data[1]['data']
print(data)
if __name__=='__main__': # 程序入口
unittest.main()