前提
概念
接口:系统之间数据交互的通道。(本质是函数(方法))
接口测试,会绕过前端,直接对服务器进行测试
实现方式
软件:
postman:使用简单,上手难度低。功能较少。
jmeter:使用难度较大。上手难度大。功能齐全。
代码:
Python + requests + Unittest
java + HttpClient
http协议
HTTP:超文本传输协议,基于请求与响应的应用层协议
默认端口:80
https默认端口:443
特点:客户端/服务器模式、简单快速、灵活、无连接、无状态
URL:协议://ip地址:端口号/资源路径
请求:
请求行:请求方法 url 协议版本
请求头:键值对
。Content-Type:作用,指定 请求体的数据类型
请求体:数据类型受Content-Type影响
1xx 指示错误
200 成功
30x 重定向错误
403 访问被禁止( 权限不足)
404 请求资源不存在
500 服务器错误
接口风格
传统风格
restful风格
测试流程
需求分析 -- 接口文档的解析 -- 设计测试用例 -- 脚本开发 -- 缺陷跟踪 -- 生成测试报告 -- 接口自动化持续集成
接口文档解析:
接口文档:又称为API文档,是由后端开发编写,用来描述接口信息的文档。
测试维度
功能测试
。 单接口测试
。 业务场景功能测试
性能测试
。 响应时长:从发送请求到接收到服务器回发响应包所经历的时间。
。 错误率:服务器运行出错的概率
。 吞吐量:服务器单位时间内,处理请求的数量。
。 服务器资源利用率:cpu、内存、网络、磁盘等 硬件资源的占用率。
安全测试
。 攻击安全:木马,病毒...
。 业务安全:敏感数据加密存储、SQL注入...
测试用例
设计方法

单接口测试:
。 正向测试
。 反向测试
业务场景测试:
。 尽量模拟用户实际使用场景
。尽量用最少的用例,覆盖最多的接口请求
。 一般情况下,覆盖正向测试即可
编写
用例编号 | 用例名称 | 模块 | 优先级 | 前置条件 | 接口名称 | 请求方法 | URL | 请求头 | 请求数据 | 预期结果 | 实际结果 |
---|
分析测试点
与功能测试时分析差不多。
Postman的使用
基本使用
1、请求方法
2、URL
3、请求头
4、请求体
5、断言
断言

pm:postman的实例
test():postman的测试方法
- 断言响应状态码
js
// 断言响应状态码200
pm.test("Status code is 200", function () {
pm.response.to.have.status(200);
});
参数一:"Status code is 200" ==》 给用户看的提示信息
参数二:"function () { pm.response.to.have.status(200);}" ==》预期结果是200。
- 断言包含的某个字符串
js
pm.test("Body matches string", function () {
pm.expect(pm.response.text()).to.include("string_you_want_to_search");
});
参数一:"Body matches string" ==》 给用户看的提示信息
参数二:"function () {pm.expect(pm.response.text()).to.include("预期字符串");}" ==》预期结果是应该包含"预期字符串"·
- 断言JSON数据
js
pm.test("Your test name", function () {
var jsonData = pm.response.json();
pm.expect(jsonData.value).to.eql(100);
});
参数一:"Your test name" ==》 给用户看的提示信息
参数二:"function () {var jsonData = pm.response.json();
pm.expect(jsonData.value).to.eql(100);}" ==》预期结果json中某个"value"的key值为"100"
原理:
关联
js
// 1、获取响应数据、转为json格式,保存到变量jsonDta 中。
var jsonDta = pm.response.json()
// 2.1、设置环境变量。
pm.environment.set( "环境变量名",环境变量值)
// 2.2、使用全局变量做容器。
pm.globals.set ( "全局变量名",全局变量值)
// 3.在postman界面中提取全局、环境变量数据。
{{全局变量名}}/{{环境变量名}}
var value = pm.environment.get("var_name"); # 代码中引用
创建环境:
全局变量:在整个postman中都可以使用的变量。不需要单独创建环境。
环境变量:在特定的环境下,才能使用的变量。需要给此变量创建单独的环境。
例子一(方法步骤):
-
1、发送获取天气的请求,获取响应结果,存入全局变量
-
2、百度搜索接口从全局变量中,取城市名,完成搜索
例子二(令牌的存储):

参数化
-
CSV 文件
注意:
1、不能测试 bool类型。因为postman读取csv后,将所有非数值类型数据,自动添加""变为字符串
2、不能存储复杂数据类型(元组,列表,字典)
3、不能实现参数测试
-
JSON文件
文件大
例如:1、创建导入
2、引用数据文件
(1、){{keyi}} 引用相关对象的key
(2、)使用postman内置的关键字data
例如: csv文件:data.字段名;
json文件:data.键名;
只能按第一图进行操作,然后点击run.
测试报告
前提:
1、安装node.js
2、安装newman【npm install -g newman】【newman -v 测试安装成功】
3、安装newman插件【npm install -g newman-reporter-htmlextra】
1、导出用例:
2、导出环境:
3、命令生成:
newman run 用例集文件.json -e 环境文件 -r htmlextra --reporter-htmlextra-export aa.html
Postman练习
1、项目结构
2、创建环境
3、编写测试用例
4、执行测试用例(批量执行)
接口自动化
Requests库
Requests库是Python编写的,基于urllib 的HTTP库,使用方便。
安装: pip install requests
请求语法:
Cookie
简介: 工程师 针对 http协议是无连接、无状态特性,设计的 一种技术。可以在浏览器端 存储用户的信息。
。 cookie 用于存储 用户临时的不敏感信息。
。 cookie 位于浏览器端。默认大小 4k(可以调整)
。 cookie 中的数据,可以随意被访问,没有安全性可言。
。 cookie 中存储的数据类型,受浏览器限制。
身份认证方式:
。 token
。 cookie +session
cookie +session认证
【就是把获取验证码的请求得到cookie,然后登录时带过去】
Session
简介: 也叫会话。通常出现在网络通信中,从客户端借助访问终端登录上服务器,直到退出登录所产生的通信数据,保存在会话中。
。 Session用于存储 用户的信息。
。 Session位于服务端。大小自接使用服务器大小。
。 Session 中的数据,不能随意被访问,安全性高。
。 Session中存储的数据类型,受服务器限制。
。 Session自动管理Cookie
session认证
获取指定响应数据:
Cookie和Seesion的区别
Cookie | Seesion | |
---|---|---|
存储位置 | 浏览器 | 服务器 |
安全性 | 可随意获取 | 加密存储 |
数据类型 | 受浏览器限制较少 | 直接使用服务器,支持使用所有数据类型 |
大小 | 默认4K | 约为服务器大小 |
参数化
1、读取Jason文件
python
def read_json_data():
list_data = []
# 从 .json 文件中,读取 [{},{},{}] 数据
with open("./params_data.json", "r", encoding="utf-8") as f:
data = json.load(f)
for item in data:
tmp = tuple(item.values()) # 将字典里的values转成元组
list_data.append(tmp) # 转成[(),(),(),()]格式
return list_data
2、参数化
步骤:
- 导包:from parameterized import parameterized
- 在通用测试方法上添加一行
- 并传参元组列表数据(调用自己封装的读取json文件的函数):@parameterized.expand(read_json_data())
- 修改通用测试方法形参,与json文件中的key保持一致
- 在通用测试方法内,使用形参
python
@parameterized.expand(read_json_data())
def test_add(self, cu1, cu2, cu3):
res = add(cu1, cu2)
self.assertEqual(cu3, res)
接口自动化框架
类似于分层架构,分包管理

目录结构:

首先在接口对象成,编写用例代码,然后在接口用例成执行代码,其中断言需要调用工具包的方法,数据需要调用数据文件
接口对象层
文件夹名:api
文件名:hr_login_api
python
import requests
class HrLoginApi(object):
# 登录
@classmethod
def login(cls, json_data):
url = "http://xxxxxxxx/xxxx/login"
header = {"Content-Type": "application/json"}
resp = requests.post(url=url, headers=header, json=json_data)
return resp
接口用例层
文件夹名:scripts
文件名:test_hr_login
python
import unittest
from parameterized import parameterized
from api.hr_login_api import HrLoginApi
from common.assert_util import assert_util
from common.read_json_util import read_json_data
class TestHrLogin(unittest.TestCase):
@parameterized.expand(read_json_data())
def test_login_sucess(self, desc, req_data, status_code, success, code, message):
# 调用自己的接口
resp = HrLoginApi.login(req_data)
# 断言
assert_util(self, resp, status_code, success, code, message)
工具类
文件夹名:common
文件名:assert_util
python
# 定义通用断言方法
def assert_util(self, resp, status_code, success, code, message):
self.assertEqual(status_code, resp.status_code)
self.assertEqual(success, resp.json().get("success"))
self.assertEqual(code, resp.json().get("code"))
self.assertIn(message, resp.json().get("message"))
文件夹名:common
文件名:read_json_util.py
python
# 定义函数读取Json文件
import json
def read_json_data():
list_data = []
with open("../data/params_data.json", "r", encoding="utf-8") as f:
json_data = json.load(f)
for item in json_data:
tem = tuple(item.values())
list_data.append(tem)
return list_data
数据文件
文件夹名:data
文件名:params_data.json
json
[
{
"desc": "登录成功",
"req_data":{"mobile": "13800000002", "password": "123456"},
"status_code": 200,
"success": true,
"code": 1000,
"message": "操作成功"
},
{
"desc": "手机号为空",
"req_data":{"mobile": null, "password": "123456"},
"status_code": 200,
"success": false,
"code": 2001,
"message": "用户名或密码错误"
}
]
测试报告文件
日志收集
log:记录当前系统运行是的信息。
作用:查看系统是否正常,定位bug
日志级别:日志级别与严重性相反 ,只会记录比自己低级别和相同级别的日志
代码如下:
python
"""
步骤:
# 0. 导包
# 1. 创建日志器对象
# 2. 设置日志打印级别
# logging.DEBUG 调试级别
# logging.INFO 信息级别
# logging.WARNING 警告级别
# logging.ERROR 错误级别
# logging.CRITICAL 严重错误级别
# 3. 创建处理器对象
# 创建 输出到控制台 处理器对象
# 创建 输出到日志文件 处理器对象
# 4. 创建日志信息格式
# 5. 将日志信息格式设置给处理器
# 设置给 控制台处理器
# 设置给 日志文件处理器
# 6. 给日志器添加处理器
# 给日志对象 添加 控制台处理器
# 给日志对象 添加 日志文件处理器
# 7. 打印日志
"""
import logging.handlers
import logging
import time
# 1. 创建日志器对象
logger = logging.getLogger()
# 2. 设置日志打印级别
logger.setLevel(logging.DEBUG)
# logging.DEBUG 调试级别
# logging.INFO 信息级别
# logging.WARNING 警告级别
# logging.ERROR 错误级别
# logging.CRITICAL 严重错误级别
# 3.1 创建 输出到控制台 处理器对象
st = logging.StreamHandler()
# 3.2 创建 输出到日志文件 处理器对象
fh = logging.handlers.TimedRotatingFileHandler('a.log', when='midnight', interval=1,
backupCount=3, encoding='utf-8')
# when 字符串,指定日志切分间隔时间的单位。midnight:凌晨:12点。
# interval 是间隔时间单位的个数,指等待多少个 when 后继续进行日志记录
# backupCount 是保留日志文件的个数
# 4. 创建日志信息格式
fmt = "%(asctime)s %(levelname)s [%(filename)s(%(funcName)s:%(lineno)d)] - %(message)s"
formatter = logging.Formatter(fmt)
# 5.1 日志信息格式 设置给 控制台处理器
st.setFormatter(formatter)
# 5.2 日志信息格式 设置给 日志文件处理器
fh.setFormatter(formatter)
# 6.1 给日志器对象 添加 控制台处理器
logger.addHandler(st)
# 6.2 给日志器对象 添加 日志文件处理器
logger.addHandler(fh)
# 7. 打印日志
while True:
# logging.debug('我是一个调试级别的日志')
logging.info('我是一个信息级别的日志')
# logging.warning('我是一个警告级别的日志')
# logging.error('我是一个错误级别的日志')
# logging.critical('我是一个严重错误级别的日志')
time.sleep(1)
封装成工具类:
python
import logging.handlers
import logging
import time
def init_log_config(filename, when='midnight', interval=1, backup_count=7):
"""
功能:初始化日志配置函数
:param filename: 日志文件名
:param when: 设定日志切分的间隔时间单位 'mindnight'
:param interval: 间隔时间单位的个数,指等待多少个 when 后继续进行日志记录
:param backup_count: 保留日志文件的个数
:return:
"""
# 1. 创建日志器对象
logger = logging.getLogger()
# 2. 设置日志打印级别
logger.setLevel(logging.DEBUG)
# logging.DEBUG 调试级别
# logging.INFO 信息级别
# logging.WARNING 警告级别
# logging.ERROR 错误级别
# logging.CRITICAL 严重错误级别
# 3. 创建处理器对象
# 控制台对象
st = logging.StreamHandler()
# 日志文件对象
fh = logging.handlers.TimedRotatingFileHandler(filename,
when=when,
interval=interval,
backupCount=backup_count,
encoding='utf-8')
# 4. 日志信息格式
fmt = "%(asctime)s %(levelname)s [%(filename)s(%(funcName)s:%(lineno)d)] - %(message)s"
formatter = logging.Formatter(fmt)
# 5. 给处理器设置日志信息格式
st.setFormatter(formatter)
fh.setFormatter(formatter)
# 6. 给日志器添加处理器
logger.addHandler(st)
logger.addHandler(fh)
if __name__ == '__main__':
# 初始化日志
init_log_config('a.log')
# 打印输出日志信息
logging.debug('我是一个调试级别的日志')
全量字段校验
1、定义校验规则
2、比对
unittest框架
主要用于管理测试用例。
注意遵守标识符命名规范:
- 只能使用 字母、数字、下划线。数字不能开头。避免使用 关键字、已知函数名
- 类:(首字母必须大写。建议以 Test 开头)
- 方法:(必须 test 开头,建议 编号)
基本用法测试
- 1、创建测试用例
python
import requests
# 导包
import unittest
class Test01(unittest.TestCase): # 使用TestCase,创建测试用例
# def 定义的test_ 是测试用例,只有执行 if __name__ == '___mian___'
# 的时候会执行测试用例,其他普通函数则不执行,通过 self 来调用执行。
def test_01(self): # 测试方法必须以test_开头
url = 'https://apiuat.jaspervault.io/api/options-dates/'
params = {'network_id': 1, 'collateral_token_id': 1, 'collateral_token_id': 2, 'collateral_token_id': 'hedge'}
req = requests.get(url, params)
print(req.json())
- 2、执行测试用例
python
import unittest
from htmltestreport import HTMLTestReport
from test.test_aa03 import Test01
# TestSuite,测试套件
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(Test01)) # 将测试类的所有方法进行添加
runner = unittest.TextTestRunner() # 实例化运行对象
runner = HTMLTestReport("测试报告1.html") # 自动化生成测试报告
runner.run(suite) # 运行对象
- 批量增加测试方法到测试套件中
写法:
- suite = unittest.TestLoader().discover("指定搜索的目录文件","指定字母开头模块文件")
- suite = unittest.defaultTestLoader.discover("指定搜索的目录文件","指定字母开头模块文件") 【推荐】
注意:
如果使用写法1,TestLoader()必须有括号。
Fixture(测试夹具)
是一种代码结构,在某些特定情况下,会自动执行。
方法级别:每个方法执行前后都要执行
def setUp(),每个测试方法执行之前都会执行 (初始化)
def tearDown(),每个测试方法执行之后都会执行 (释放)
类级别:在每个测试类中所有方法执行前后 都会自动调用的结构(在整个类中 执行之前执行之后各一次)
类方法必须使用 @classmethod修饰
def setUpClass() ,类中所有方法之前
def tearDownClass(),类中所有方法之后
断言
-
断言的结果:
1、True,用例通过
2、False,代码抛出异常,用例不通过
3、在unittest中使用断言,需要通过 self.断言方法
-
常用的断言:
txt
self.assertEqual(ex1, ex2) # 判断ex1 是否和ex2 相等
self.assertIn(ex1, ex2) # ex2是否包含 ex1 注意:所谓的包含不能跳字符
self.assertTrue(ex) # 判断ex是否为True
重点讲前两个assertEqual 和 assertIn
方法:
assertEqual:self.assertEqual(预期结果,实际结果) 判断的是预期是否相等实际
assertIn:self.assertIn(预期结果,实际结果) 判断的是预期是否包含实际中
assertIn('admin', 'admin') # 包含
assertIn('admin', 'adminnnnnnnn') # 包含
assertIn('admin', 'aaaaaadmin') # 包含
assertIn('admin', 'aaaaaadminnnnnnn') # 包含
assertIn('admin', 'addddddmin') # 不是包含
标准语法
python
import unittest
class demo(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None: # 执行该类前进行
print("setUpClass")
@classmethod
def tearDownClass(cls) -> None:
print("tearDownClass") # 执行该类后进行
def setUp(self) -> None: # 执行每个用例前进行
print("setup")
def tearDown(self) -> None: # 执行每个用例后进行
print("down")
def test_case01(self):
print("test_case01")
self.assertEqual(1, 1, "判断相等")
@unittest.skip # 跳过该用例
def test_case02(self):
print("test_case02")
self.assertEqual(2, 1, "判断相等")
PyMySQL操作数据库
使用场景:校验测试数据(字段会改,返回数据没有),构造测试数据(数据只能用一次,无法保证数据已存在)
安装
pip install pymysql
操作步骤

1.导包import pymysql
2.创建连接。conn=pymysql.connect()
3.获取游标。cursor=conn.cursor()
4.执行 SQL。cursor.execute("sql语句")
5.关闭游标,关闭连接
1.导包
import pymysql
2、创建连接
conn = pymysql.connect(host=" ",port=0,user=" ",password=" ",database=" ",charset=" ")
host:数据库所在IP地址
port:端口
user:用户名
password:密码
database:连接那个数据库的名字
charset:字符集。utf8
conn :数据库对象
3、获取游标
cursor=conn.cursor()
4、执行SQL语句
。 查询语句
提取数据:cursor.fetch*()
。 增删改语句
成功:提交事务 conn.commit()
失败:回滚事务 conn.rollback()
【查询】
cursor.fetch*() #默认在0号位,提取数据在所在位置的下一行,每提一条自动向下移动。
。。 fetchone():从结果集中,提取一行。
。。 fetchmany(size):从结果集中,提取 size 行。
。。 fetchall():提取所有结果集。
。。 属性rownumber:可以设置游标位置。 cursor.rownumber=0
【添加】【修改】
。。 成功:提交事务 conn.commit()
。。 失败:回滚事务 conn.rollback()
5、关闭
关闭游标:cursor.close()
关闭连接:conn.close()
封装
pymysql封装
python
import pymysql
class DBUtil(object):
conn = None
cursor = None
@classmethod
def __get_conn(cls):
if cls.conn is None:
cls.conn = pymysql.connect(host="127.0.0.1", port=3306, user="root",
password="root", database="test01", charset="utf8")
return cls.conn
@classmethod
def __close_conn(cls):
if cls.conn is not None:
cls.conn.close()
cls.conn = None
pass
@classmethod
def select_one(cls, sql):
try:
# 获取连接
cls.conn = cls.__get_conn()
# 执行sql语句
cursor = cls.conn.cursor()
cursor.execute(sql)
res = cursor.fetchone()
except Exception as err:
print("查询sql错误", str(err))
finally:
# 关闭连接
cls.__close_conn()
return res
@classmethod
def uid_db(cls, sql):
cursor = None
try:
# 连接
cls.conn = cls.__get_conn()
cursor = cls.conn.cursor()
# sql
cursor.execute(sql)
print("影响行数:", cls.conn.affected_rows())
# 提交
cls.conn.commit()
except Exception as err:
print("err", err)
# 回滚
cls.conn.rollback()
finally:
# 关闭
cursor.close()
cls.__close_conn()
测试
简单来说就是将重复的代码进行封装
- 接口对象层
封装接口的 - 测试脚本层
封装断言的