分享做接口测试时,搭建的测试框架思路
- 使用环境
- windows 10
- python3.7 + excel + unittest + requests + + logger + database + htmlreport
目前实现功能点:
- 核心通用测试框
- token关联
- 上个接口结果的值赋予下个接口的请求参数等
- 日志模块
- 数据库模块
- 环境配置模块
- 操作excel模块
- 测试报告模块
目录
考虑因素:
接口数量:接口数量多,若放在脚本中维护不方便,所以使用excel来维护接口用例
一、excel元素设计
1、主用例元素
API接口常用的一些元素
元素 | 说明 | 例子 |
---|---|---|
id | 用例id(自定义) | case_0001 |
title | 用例标题 | 验证正常登录 |
run | 是否运行标识,yes: 运行 no:不运行 | yes |
url | 主用例接口 | /Hardware/api/Test/ |
method | 请求方法(get/post/put等) | post |
headers | 请求头 | {“Content-Type”: “application/json”} |
request_data | 请求参数 | {“key”: “data”} |
result | 记录请求结果 | {“ResponseStatus”:{“ErrorCode”:“0”}} |
expect | 预期结果 | {“ErrorCode”:“0”} |
2、依赖用例元素
主用例需要依赖的接口,可以做到上个接口的结果值赋予主用例接口的请求参数
元素 | 说明 | 例子 |
---|---|---|
depend_url | 主用例需要依赖的接口参数 | {“url”:"/depend_url", “method”:“Post”,“content”:“接口描述”} |
depend_request_data | 依赖接口的请求参数 | {“key”: “depend_data”} |
depend_result | 记录依赖接口的请求结果 | {“ResponseStatus”:{“ErrorCode”:“0”}} |
get_depend_result_for_main_case | 获取依赖请求结果的某个参数赋予主用例的请求参数 | {“depend_key”:“main_key”} |
3、sql元素
实际项目中,有几种情况需要用到查询获取值
1.API接口中,传入的参数是数据库中的某个字段值
2.请求结果的值=数据库中的某个字段值(断言用)
元素 | 说明 | 例子 |
---|---|---|
sql | 查询数据库,并获取值赋予对应参数 | [{“sql”:“select key from table”, “key”:“request_key”}] |
预览
二、操作excel
上面讲了接口用例的元素设计,接下来用脚本处理
data_config:excel元素索引配置
# coding:utf-8
# 根据索引,分别对应excel中的元素,若元素有改动,在这里维护即可
class Global_Val(object):
Id = 0
title = 1
run = 2
url = 3
request_method = 4
header = 5
depend_url = 6
depend_data = 7
depend_result = 8
depend_key = 9
data = 10
sql = 11
expect = 12
result = 13
is_success = 14
def get_id():
"""获取case_id"""
return Global_Val.Id
def get_title():
"""获取用例标题"""
return Global_Val.title
def get_url():
"""获取请求url"""
return Global_Val.url
def get_run():
"""获取是否运行"""
return Global_Val.run
def get_request_method():
"""获取请求方式"""
return Global_Val.request_method
def get_header():
"""获取header"""
return Global_Val.header
def get_depend_case():
"""case依赖"""
return Global_Val.depend_url
def get_depend_data():
"""依赖请求数据"""
return Global_Val.depend_data
def get_depend_result():
"""依赖请求结果"""
return Global_Val.depend_result
def get_depend_result_key():
"""依赖请求结果参数"""
return Global_Val.depend_key
def get_request_data():
"""请求数据"""
return Global_Val.data
def get_sql():
"""sql数据"""
return Global_Val.sql
def get_expect():
"""预期结果"""
return Global_Val.expect
def get_result():
"""实际结果"""
return Global_Val.result
def get_is_success():
"""成功/失败结果"""
return Global_Val.is_success
get_data:获取excel数据
operation_excel:封装操作excel
import json
from data import data_config
from common.operation_excel import OperationExcel
class GetData(object):
"""获取excel数据"""
def __init__(self, file_path, sheet_name):
self.opera_excel = OperationExcel(file_path, sheet_name)
def get_case_lines(self):
"""获取excel行数,即case的个数"""
return self.opera_excel.get_max_rows()
def get_is_run(self, row):
"""获取是否执行"""
col = int(data_config.get_run())
run_model = self.opera_excel.get_cell_value(row, col)
if run_model == 'yes':
flag = True
else:
flag = False
return flag
def is_header(self, row):
"""
获取header
:param row: 行号
:return:
"""
col = int(data_config.get_header())
header = self.opera_excel.get_cell_value(row, col)
if header != '':
return json.loads(header)
else:
return None
def get_request_method(self, row):
"""
获取请求方式
:param row: 行号
:return:
"""
# col 列
col = int(data_config.get_request_method())
request_method = self.opera_excel.get_cell_value(row, col)
return request_method
def get_request_url(self, row):
"""
获取url
:param row: 行号
:return:
"""
col = int(data_config.get_url())
url = self.opera_excel.get_cell_value(row, col)
return url
def get_request_data(self, row):
"""
获取请求数据
:param row:行号
:return:
"""
col = int(data_config.get_request_data())
data = self.opera_excel.get_cell_value(row, col)
if data == '':
return None
# return json.loads(data)
return eval(data)
def get_expect_data(self, row):
"""
获取预期结果
:param row:
:return:
"""
col = int(data_config.get_expect())
expect = self.opera_excel.get_cell_value(row, col)
if expect == "":
return None
else:
return eval(expect)
def write_result(self, row, value):
"""
写入结果数据
:param value: 结果
:param row: 行号
:return:
"""
col = int(data_config.get_result())
self.opera_excel.write_value(row, col, value)
def write_result_depend(self, row, value):
"""
写入依赖结果数据
:param value: 结果
:param row: 行号
:return:
"""
col = int(data_config.get_depend_result())
self.opera_excel.write_value(row, col, value)
def write_is_success(self, row, value):
"""
写入结果数据
:param value: 结果
:param row: 行号
:param col: 列号
:return:
"""
col = int(data_config.get_is_success())
self.opera_excel.write_value(row, col, value)
def get_depend_data(self, row):
"""
获取依赖数据的key
:param row:行号
:return:
"""
col = int(data_config.get_depend_data())
depend_data = self.opera_excel.get_cell_value(row, col)
if depend_data == "":
return None
else:
return json.loads(depend_data)
def get_depend_key(self, row):
"""
获取依赖请求结果的某个参数值
:param row: 行号
:return:
"""
col = int(data_config.get_depend_result_key())
depend_key = self.opera_excel.get_cell_value(row, col)
if depend_key == "":
return None
else:
return eval(depend_key)
def is_depend(self, row):
"""
判断是否有case依赖
:param row:行号
:return:
"""
col = int(data_config.get_depend_case()) # 获取是否存在数据依赖列
depend_url = self.opera_excel.get_cell_value(row, col)
if depend_url == "":
return None
else:
return depend_url
def get_title(self, row):
"""
获取用例标题
:param row: 行号
:return:
"""
col = int(data_config.get_title())
title = self.opera_excel.get_cell_value(row, col)
if title == "":
return None
else:
return title
def get_sql(self, row):
"""
获取sql
:param row: 行号
:return:
"""
col = int(data_config.get_sql())
sql = self.opera_excel.get_cell_value(row, col)
if sql == "":
return None
else:
# return json.loads(sql)
return eval(sql)
三、封装HTTP请求
runmethod:使用requests库,封装HTTP请求方法
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import requests
import urllib3
class RunMethod:
def __init__(self):
self.session = requests.session()
urllib3.disable_warnings()
def run_main(self, method, url, data=None, header=None, params=None):
"""
归类请求方法
:param method: 请求方法
:param url: 接口
:param data: 请求参数
:param header: 请求头
:return: response(请求结果)
"""
if method.lower() == 'Post'.lower(): # 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST 请求可能会导致新的资源的建立和/或已有资源的修改。
res = self.session.post(url=url, json=data, headers=header, verify=False)
elif method.lower() == 'Get'.lower(): # 请求指定的页面信息,并返回实体主体。
res = self.session.get(url=url, json=data, headers=header, params=params, verify=False)
elif method.lower() == 'HEAD'.lower(): # 类似于 GET 请求,只不过返回的响应中没有具体的内容,用于获取报头
res = self.session.head(url=url, json=data, headers=header, verify=False)
elif method.lower() == 'PUT'.lower(): # 从客户端向服务器传送的数据取代指定的文档的内容。
res = self.session.put(url=url, json=data, headers=header, verify=False)
elif method.lower() == 'DELETE'.lower(): # 请求服务器删除指定的页面。
res = self.session.delete(url=url, json=data, headers=header, verify=False)
elif method.lower() == 'OPTIONS'.lower(): # 允许客户端查看服务器的性能。
res = self.session.options(url=url, json=data, headers=header, verify=False)
elif method.lower() == 'PATCH'.lower(): # 是对 PUT 方法的补充,用来对已知资源进行局部更新
res = self.session.patch(url=url, json=data, headers=header, verify=False)
else:
res = self.session.get(url=url, json=data, headers=header, verify=False)
return res
if __name__ == '__main__':
url = 'http://httpbin.org/post'
data = {
'cart': '11'
}
header = {
"Content-Type": "text/html123123; charset=utf-8",
"Accept-Charset": "utf-8"
}
run = RunMethod()
run_test = run.run_main(method="Post", url=url, data=data, header=header)
res_json = run_test.json()
print(run_test.json())
requests库模块详解可以参考博客:https://www.jianshu.com/p/ada99b7880a6
四、核心逻辑
接口的核心逻辑,这里没结合unittest来做自动化,先讲一下核心逻辑:
from base.runmethod import RunMethod
from data.get_data import GetData
def base_test():
"""
基础测试框
"""
data_case = GetData(file_path, sheet_name) # 测试用例对象
case_count = data_case.get_case_lines # 用例数
for i in range(1, int(case_count)):
is_run = data_case.get_is_run(i) # 是否运行
if is_run:
url = BASE_URL + data_case.get_request_url(i) # 用例url
method = data_case.get_request_method(i) # 请求方法
request_data = data_case.get_request_data(i) # 请求数据
expect = data_case.get_expect_data(i) # 预期结果
header = data_case.is_header(i) # 请求头
depend = data_case.is_depend(i) # 依赖
depend_data = data_case.get_depend_data(i) # 依赖请求数据
depend_key = data_case.get_depend_key(i) # 依赖关键字参数
sql_data = data_case.get_sql(i) # 获取sql列表
# 执行主用例
res = RunMethod().run_main(method=method, url=url, data=request_data, header=header)
# 断言(放后面讲)
base_assert(i, url, header, request_data, expect, res, data_case)
到这里我们已经拿到了所有用例数据,在执行主用例之前,我们需要关注两个逻辑处理
1)查询数据库逻辑
项目中,我们经常需要用到数据库,在接口的输入参数中,有时是从数据库中存放的某个值,
同样的,在接口的输出结果参数中,有时也是存放于数据库中的某个值。
因此,我们可以设计使用sql获取值并赋予接口的输入参数或预期结果的参数
excel中sql列的内容格式:列表+字典,每个字典两个键值对,一个sql键值对,一个赋予对象键值对
例子:
[{“sql”:“select name from table + 条件”, “request”: “请求参数的某个键”}]
简单理解就是一个字典:一条sql,获取一个值,一个赋予对象(请求/依赖/预期),一个赋予对象数据的键,并且以列表为容器,支持多个字典获取值并赋予不同对象,理论上无限(sql很灵活,可以根据不同查询条件获取不同类型的值)
具体的格式,各位可以根据自己的项目实际情况来定制,这里提供的是一个思路,不一定是一成不变的
# 执行查询数据库逻辑
if sql_data is not None:
for data in sql_data:
sql = data["sql"] # 获取sql
result = DB.ExecQuery(sql) # 使用sql第三方库,后面讲
if result:
result = str(result[0][0])
else:
raise ValueError("查询结果为空,请检查sql或数据")
# 根据key赋值到请求数据/依赖请求数据/预期结果
for key in data.keys():
if key.lower() == "request":
for_request = data[key]
request_data[for_request] = result
elif key.lower() == "expect":
for_expect = data[key]
expect[for_expect] = result
elif key.lower() == "depend":
for_depend = data[key]
depend_data[for_depend] = result
elif key.lower() == "sql":
pass
else:
raise ValueError("键应为 1.request 2.expect 3.depend,请检查键是否正确")
2)依赖接口逻辑
有一些流程性的接口,上下接口有关联性,例如上个接口的输出结果中带有下一个接口输入参数需要的值,这种情况下,若只运行单个接口,而没有上个接口的输出结果参数,结果是错误的。
因此我们可以设计依赖接口逻辑,同样也是在excel中维护(excel中依赖接口的元素设计在目录一excel元素设计有讲到)
depend_url: 字典,存放url和请求方法、content为接口的描述
例子:
{
“url”:"/Hardware/TestAPI",
“method”:“Post”,
“content”:“XX接口”,
}
depend_request_data: 依赖接口的请求数据
例子:{“UserName”: “zhangsan” , “PassWord”: “123456”}
depend_result: 记录请求依赖接口的结果
例子:{“ResponseStatus”:“ErrorCode”:“0”}
get_depend_result_for_main_case: 以字典为容器,键为依赖结果的某个键,值为主用例请求数据的某个键,确定好对象,脚本中自动处理将依赖结果值赋予主用例请求数据
例子:
依赖接口的请求结果为:{“OrderID”: “DD123456”, “ResponseStatus”:“ErrorCode”:“0”}
主用例的请求数据为:{“Amount”:3, “OrderNum”: “”}
若主用例请求数据的"OrderNum"键需要依赖结果的"OrderID"的值
则填写为:{“OrderID”:“OrderNum”},即将OrderID的值赋予主用例的OrderNum键的值
# 执行依赖接口逻辑
if depend is not None:
depend = eval(depend) # 转字典
depend_url = BASE_URL + depend["url"] # 依赖接口
depend_method = depend["method"] # 请求方式
depend_content = depend.get("content") # 接口描述
res = RunMethod().run_main(method=depend_method, url=depend_url, data=depend_data, header=header)
data_case.write_result_depend(i, res.text) # 写入请求结果到excel中
# 更新依赖值到请求数据中
if depend_key is not None:
for key, value in depend_key.items():
res_value = get_keyValue(res=res.json(), key=key)
request_data[value] = res_value
同上,各位可以根据自己的需求来设计内容
3)执行顺序:数据库逻辑>依赖接口逻辑
执行完两个逻辑后,即可执行主用例和断言
# 执行主用例
res = RunMethod().run_main(method=method, url=url, data=request_data, header=header)
# 断言(放后面讲)
base_assert(i, url, header, request_data, expect, res, data_case)
五、断言
执行完主用例后,我们需要判断请求结果是否符合我们的预期结果
设计思路:
1.实际项目中,请求的结果是存在变化性的,例如一个搜索接口,相同的搜索条件,多次请求,请求期间,业务数据有变化,多次请求的结果是不相同的,因此,不适合将整个请求结果作为预期结果来判断是否相等
2.请求的结果的关键字,实际项目中,请求的关键字一般都有会有一个code,代表这次请求结果是否正确,例如:{“code”:0,“whwswswws”:“atOSfiKraviqFf/xSxol68A==”,“openall”:1,“openalltouch”:1,“processtype”:1}
3.除了code关键字,其他关键字根据情况也可以加入预期结果进行断言
因此,我们可以设计请求结果的关键字和预期的值组成字典键值对,来进行断言,例如:{“code”:0, “processtype”:1};话不多说,上代码
def base_assert(i, url, header, request_data, expect, res, data_case):
"""
通用断言,根据预期结果键值对>断言>实际结果,相等则成功,反之失败
:param i: 用例行
:param url: 用例接口
:param header: 请求头
:param request_data: 请求数据
:param expect: 预期结果
:param res: 实际结果
:param data_case: 用例
:return: 是否成功标识
"""
expect_keys = list(expect.keys())
len_keys = len(expect_keys)
global isSuccess
for index, key in enumerate(expect_keys):
# 最后键标识位
if len_keys == index + 1:
is_last = True
else:
is_last = False
# 根据预期结果的键查询响应结果对应键,并断言
if key in res.json().keys():
e = type(res.json()[key])(expect[key]) # 转换expect值的type为result值的type
try:
e == res.json()[key]
except AssertionError as msg:
data_case.write_result(i, res.text) # 回写实际结果到excel
data_case.write_is_success(i, "Fail") # 回写成功结果到excel
isSuccess = False
raise AssertionError(msg)
else:
if is_last is True:
data_case.write_result(i, res.text)
data_case.write_is_success(i, "Success")
isSuccess = True
else:
for k, v in res.json().items():
v_type = type(v)
while v_type != dict:
if v_type is list:
v_list = v
for vv in v_list:
if type(vv) is dict:
v = vv
v_type = dict
else:
break
else:
if key in v.keys():
e = type(v[key])(expect[key]) # 转换expect值的type为result值的type
try:
e == res.json()[key]
except AssertionError as msg:
data_case.write_result(i, res.text)
data_case.write_is_success(i, "Fail")
isSuccess = False
raise AssertionError(msg)
else:
if is_last is True:
data_case.write_result(i, res.text)
data_case.write_is_success(i, "Success")
isSuccess = True
return isSuccess
六、结合unittest+ddt执行测试用例
unittest是python的单元测试框架,可以用来执行我们的测试用例和输出HTML报告
根据unittest的特性:
1.决定写一个继承unittest.TestCase的基类:base_case;
2.测试用例放在test_case文件夹中维护,这样比较清晰;
base_case:核心基础类
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import time
import unittest
import uuid
import warnings
from base.runmethod import RunMethod
from base.base_config import BASE_URL, DB_MD
from common.check import check_response_status
from common.logger import Logger
from configs import config_manage
class BaseCase(unittest.TestCase):
"""
setUpClass:每次运行脚本,优先执行,只执行一次
tearDownClass:脚本运行完成后执行,只执行一次
"""
@classmethod
def setUpClass(cls) -> None:
warnings.simplefilter('ignore', ResourceWarning)
cls.run_method = RunMethod()
pass
@classmethod
def tearDownClass(cls) -> None:
pass
"""
setUp:每执行一条用例前,优先执行一次
tearDown:每执行完一条用例后,执行一次
"""
def setUp(self) -> None:
self.log = Logger(custom_color=True, name='base_case')
self.log.info("--------------------【{}测试用例开始】--------------------".format(self.__class__.__name__ + '.' + self._testMethodName))
def tearDown(self) -> None:
self.log.info("--------------------【{}测试用例结束】--------------------\n".format(self.__class__.__name__ + '.' + self._testMethodName))
self.log.del_handler() # 防止重复写入日志
def base_test(self, i, data_case):
"""
核心逻辑框,所有用例都执行这里的逻辑(登录接口另外单独一个函数)
:param i: 用例行
:param data_case: 测试用例
:return:
"""
is_run = data_case.get_is_run(i) # 是否运行
if is_run:
url = BASE_URL + data_case.get_request_url(i) # 用例url
method = data_case.get_request_method(i) # 请求方法
request_data = data_case.get_request_data(i) # 请求数据
expect = data_case.get_expect_data(i) # 预期结果
header = data_case.is_header(i) # 请求头
depend = data_case.is_depend(i) # 依赖
depend_data = data_case.get_depend_data(i) # 依赖请求数据
depend_key = data_case.get_depend_key(i) # 依赖关键字参数
sql_data = data_case.get_sql(i) # 获取sql列表
header["Authorization"] = config_manage.read_token()["Data"]["Token"] # 更新token到请求头中
# 执行查询数据库逻辑
if sql_data is not None:
for data in sql_data:
sql = data["sql"] # 获取sql
result = DB_MD.ExecQuery(sql)
if result:
result = str(result[0][0])
else:
raise ValueError("查询结果为空,请检查sql或数据")
# 根据key赋值到请求数据/依赖请求数据/预期结果
for key in data.keys():
if key.lower() == "request":
for_request = data[key]
request_data = self.get_sql_value_to_request_data(request_data=request_data, key=for_request, result=result)
elif key.lower() == "expect":
for_expect = data[key]
expect[for_expect] = result
elif key.lower() == "depend":
for_depend = data[key]
depend_data = self.get_sql_value_to_request_data(request_data=depend_data, key=for_depend, result=result)
elif key.lower() == "sql":
pass
else:
raise ValueError(self.log.info("键应为 1.request 2.expect 3.depend,请检查键是否正确"))
# 执行依赖接口逻辑
if depend is not None:
depend = eval(depend) # 转字典
depend_url = BASE_URL + depend["url"] # 依赖接口
depend_method = depend["method"] # 请求方式
depend_content = depend.get("content") # 接口描述
res = RunMethod().run_main(method=depend_method, url=depend_url, data=depend_data,
header=header)
data_case.write_result_depend(i, res.text)
self.log.info("**********执行依赖:{}**********".format(depend_content))
self.log.info("请求接口: " + depend_url)
self.log.info("请求头:" + str(header))
self.log.info("请求数据:" + str(depend_data))
self.log.info("返回响应: " + res.text)
check_response_status(res, isprint=False) # 检查执行是否成功
# 更新依赖值到请求数据中
if depend_key is not None:
for key, value in depend_key.items():
res_value = self.get_keyValue(res=res.json(), key=key)
request_data[value] = res_value
self.log.info("**********执行主用例**********")
res = RunMethod().run_main(method=method, url=url, data=request_data, header=header)
self.base_assert(i, url, header, request_data, expect, res, data_case) # 断言
def base_login(self, i, data_case):
"""
登录接口用,在登陆成功后,写入token到文件中
:param i: 用例行
:param data_case: 测试用例对象
:return:
"""
is_run = data_case.get_is_run(i)
if is_run:
url = BASE_URL + data_case.get_request_url(i) # 用例url
method = data_case.get_request_method(i) # 请求方法
request_data = data_case.get_request_data(i) # 请求数据
expect = data_case.get_expect_data(i) # 预期结果
header = data_case.is_header(i) # 请求头
depend = data_case.is_depend(i) # 依赖url相关数据
depend_data = data_case.get_depend_data(i) # 依赖请求数据
depend_key = data_case.get_depend_key(i) # 依赖关键字参数
# sql_data = data_case.get_sql(i) # 获取sql列表
"""若需要执行数据库逻辑,或依赖接口逻辑,看base_test"""
self.log.info("执行主用例".center(20, "*"))
res = RunMethod().run_main(method=method, url=url, data=request_data, header=header)
is_success = self.base_assert(i, url, header, request_data, expect, res, data_case)
# 写入token到token.json文件
if is_success is True:
config_manage.write_token(res.json())
def base_assert(self, i, url, header, request_data, expect, res, data_case):
"""
通用断言,根据预期结果键值对>断言>实际结果,相等则成功,反之失败
:param i: 用例行
:param url: 用例接口
:param header: 请求头
:param request_data: 请求数据
:param expect: 预期结果
:param res: 实际结果
:param data_case: 用例
:return: 是否成功标识
"""
expect_keys = list(expect.keys())
len_keys = len(expect_keys)
global isSuccess
for index, key in enumerate(expect_keys):
# 最后键标识位
if len_keys == index + 1:
is_last = True
else:
is_last = False
# 根据预期结果的键查询响应结果对应键,并断言
if key in res.json().keys():
e = type(res.json()[key])(expect[key]) # 转换expect值的type为result值的type
try:
self.assertEqual(e, res.json()[key])
except AssertionError as msg:
self.log.info("请求接口: " + url)
self.log.info("请求头: " + str(header))
self.log.info("请求数据: " + str(request_data))
self.log.info("实际结果: " + res.text)
self.log.info("预期结果: " + str(expect))
data_case.write_result(i, res.text)
data_case.write_is_success(i, "Fail")
isSuccess = False
self.log.error("断言失败")
raise AssertionError(msg)
else:
if is_last is True:
self.log.info("请求接口: " + url)
self.log.info("请求头: " + str(header))
self.log.info("请求数据: " + str(request_data))
self.log.info("预期结果:" + str(expect))
self.log.info("实际结果:" + res.text)
self.log.info("断言成功")
data_case.write_result(i, res.text)
data_case.write_is_success(i, "Success")
isSuccess = True
else:
for k, v in res.json().items():
v_type = type(v)
while v_type != dict:
if v_type is list:
v_list = v
for vv in v_list:
if type(vv) is dict:
v = vv
v_type = dict
else:
break
else:
if key in v.keys():
e = type(v[key])(expect[key]) # 转换expect值的type为result值的type
try:
self.assertEqual(e, v[key])
except AssertionError as msg:
self.log.info("请求接口: " + url)
self.log.info("请求头: " + str(header))
self.log.info("请求数据: " + str(request_data))
self.log.info("实际结果: " + res.text)
self.log.info("预期结果: " + str(expect))
data_case.write_result(i, res.text)
data_case.write_is_success(i, "Fail")
isSuccess = False
self.log.error("断言失败")
raise AssertionError(msg)
else:
if is_last is True:
self.log.info("请求接口: " + url)
self.log.info("请求头: " + str(header))
self.log.info("请求数据: " + str(request_data))
self.log.info("预期结果:" + str(expect))
self.log.info("实际结果:" + res.text)
self.log.info("断言成功")
data_case.write_result(i, res.text)
data_case.write_is_success(i, "Success")
isSuccess = True
return isSuccess
if __name__ == '__main__':
unittest.main()
test_case:测试用例
import unittest
from base.base_case import BaseCase
from ddt import ddt, data
from data.get_data import GetData
# excel用例路径
file_path = r'E:\PyProject\Test_Pratice\test_excel.xls'
@ddt
class Test(BaseCase):
# @unittest.skip
"""使用ddt的data方法,传递一个用例行组成的数字列表,来循环执行用例(这里可以封装一下数字列表和data_case,让代码行看起来简洁一些)"""
@data(*[num for num in range(1, GetData(file_path, sheet_name='Login').get_case_lines())])
def test_01_Login(self, i):
data_case = GetData(file_path, sheet_name='Login')
super().base_login(i, data_base)
# @unittest.skip
@data(*[num for num in range(1, GetData(file_path, sheet_name='AddGoods').get_case_lines())])
def test_02_AddGoods(self, i):
data_case = GetData(file_path, sheet_name='AddGoods')
super().regist_and_login(i, data_base)
if __name__ == '__main__':
unittest.main()
pass
关联token: base_case中已经给出示例;要关联token需要2步:
1、在base_login中,执行登录接口成功后,将带有token的请求结果写入token.json文件
2、在base_test中,执行用例之前,将token赋予到请求头中
token.json:准备json文件,放在你的项目路径下
读写token:
import json
"""TOKEN_PATH:token.json文件的路径"""
def write_token(token):
with open(TOKEN_PATH, 'w') as fp:
fp.write(json.dumps(token))
def read_token():
with open(TOKEN_PATH, 'r') as fp:
dic_data = json.load(fp)
return dic_data
七、结合unittest+HTMLTestRunner输出报告
run_all_test_case:运行所有测试用例并输出HTML报告
import unittest
import HTMLTestRunner
from configs import config_manage
import time
if __name__ == "__main__":
"""
config_manage.TESTCASE_PATH:换成你测试用例存放的路径
config_manage.REPORT_PATH:换成你测试报告想存放的路径
"""
suite = unittest.defaultTestLoader.discover(config_manage.TESTCASE_PATH,
pattern='test_*.py',
top_level_dir=None, ) # 加载路径下的所有Test开头的用例
now = time.strftime("%Y-%m-%d %H-%M-%S")
html_file = config_manage.REPORT_PATH + now + "_TestResult_Report.html" # 每次生成一份新的测试报告
# html_file = config_manage.REPORT_PATH + "TestResult_Report.html" # 固定生成一份测试报告
'''wb以二进制格式打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。'''
fp = open(html_file, "wb")
runner = HTMLTestRunner.HTMLTestRunner(stream=fp, title='接口自动化测试报告', description='用例执行情况:', )
runner.run(suite)
fp.close()
HTMLTestRunner库:放在python版本的Lib目录下即可,例如:E:\Python37\Lib
链接:https://pan.baidu.com/s/1xTRX1X5S-zagkILEDVIMtQ
提取码:c9a1
效果:
分享下BeautifulReport库,我觉得这个更好看点
import unittest
from BeautifulReport import BeautifulReport
from configs import config_manage
import time
if __name__ == "__main__":
"""
config_manage.TESTCASE_PATH:换成你测试用例存放的路径
config_manage.REPORT_PATH:换成你测试报告想存放的路径
"""
suite = unittest.defaultTestLoader.discover(config_manage.TESTCASE_PATH,
pattern='test_*.py',
top_level_dir=None, ) # 加载路径下的所有Test开头的用例
now = time.strftime("%Y-%m-%d %H-%M-%S")
html_file = config_manage.REPORT_PATH + now + "_TestResult_Report.html" # 每次生成一份新的测试报告
# html_file = config_manage.REPORT_PATH + "TestResult_Report.html" # 固定生成一份测试报告
runner = unittest.TextTestRunner()
BeautifulReport(suite).report(filename=now + "_TestResult_Report.html", description='接口测试', log_path=config_manage.REPORT_PATH)
runner.run(suite)
效果:
八、环境配置
配置是项目中必不可少的一部分,我们经常会用到各种不同的配置,例如:IP,账号,密码,数据库配置,项目路径等,若分散在不同文件中,散乱且难维护,所以我们可以设计一个存放配置的方法
configs.yaml:yaml文件存放配置参数(yaml模块安装和使用讲解:yaml模块)
# 账号配置
URL: http://localhost:9998
数据:
用户名: chen
密码: 123456
# 数据库配置
sqlServer:
host: .
port: 1433
user: sa
pwd: 123456
database: Tester
config_manage:管理环境配置参数
# coding:utf-8
import yaml
import os
import json
def get_yaml_config():
f = open(YAML_PATH, 'r', encoding='UTF-8').read() # 只读模式打开并读取字符串
dic = yaml.load(f, Loader=yaml.FullLoader) # 将配置参数转为字典格式
return dic
if __name__ == "__main__":
get_config = get_yaml_config()
print(get_config['URL'])
我们经常需要写入文件,或者读取目录/文件,例如:读取/写入excel、日志、报告等存放的路径,所以我们也可以设置一些项目文件目录的常量,统一管理,在操作的时候,直接使用定义好的常量即可;
用的是os库,想了解可以看:os模块
# 项目相关文件目录常量
PROJECT_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) # 项目路径
TESTDATA_PATH = os.path.join(PROJECT_PATH, "test_data") # 测试数据目录
TESTCASE_PATH = os.path.join(PROJECT_PATH, "test_case" + os.sep) # 测试用例目录
LOG_PATH = os.path.join(PROJECT_PATH, "test_report" + os.sep + "logs" + os.sep) # 日志目录
REPORT_PATH = os.path.join(PROJECT_PATH, "test_report" + os.sep + "report" + os.sep) # 测试报告目录
CONFIG_PATH = os.path.join(PROJECT_PATH, "configs" + os.sep) # 配置文件目录
TOKEN_PATH = os.path.join(CONFIG_PATH, "token.json") # token文件
完整代码:
# coding:utf-8
import yaml
import os
import json
# 项目相关文件目录常量
PROJECT_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) # 项目路径
TESTDATA_PATH = os.path.join(PROJECT_PATH, "test_data") # 测试数据目录
TESTCASE_PATH = os.path.join(PROJECT_PATH, "test_case" + os.sep) # 测试用例目录
LOG_PATH = os.path.join(PROJECT_PATH, "test_report" + os.sep + "logs" + os.sep) # 日志目录
REPORT_PATH = os.path.join(PROJECT_PATH, "test_report" + os.sep + "report" + os.sep) # 测试报告目录
CONFIG_PATH = os.path.join(PROJECT_PATH, "configs" + os.sep) # 配置文件目录
TOKEN_PATH = os.path.join(CONFIG_PATH, "token.json") # token文件
def get_yaml_config():
f = open(YAML_PATH, 'r', encoding='UTF-8').read() # 只读模式打开并读取字符串
dic = yaml.load(f, Loader=yaml.FullLoader) # 将配置参数转为字典格式
return dic
def write_token(token):
with open(TOKEN_PATH, 'w') as fp:
fp.write(json.dumps(token))
def read_token():
with open(TOKEN_PATH, 'r') as fp:
dic_data = json.load(fp)
return dic_data
if __name__ == "__main__":
get_config = get_yaml_config()
print(get_config['URL'])
data = {"token": "test"}
write_token(token=data)
print(read_token())
后部分有时间更新
九、日志模块
十、数据库操作模块
十一、发送邮件模块
PS:有缘的人儿,有收获的话,留个爪点个赞呗
来源:oschina
链接:https://my.oschina.net/u/4335112/blog/4281720