11.6.6 设置共享夹具
接口操作中,绝大部分接口都需要用户认证,因此保持会话就非常关键。这个时候,可以在共享夹具文件conftest.py中添加一个session函数,用以保持会话。在core\conftest.py文件中添加会话函数,代码如下:
# chapter11\core\conftest.py
import pytest
from core.send_request import set_session
@pytest.fixture(scope="session")
def session():
"""通过session维持会话"""
session = set_session()
yield session
session.close()
在函数session中直接调用core/send_request.py文件中封装好的设置会话函数set_session,在每次执行接口测试项目前设置一次session,接口测试项目执行后关闭会话。
11.6.7 封装断言函数
在11.6.1节设计测试用例文件时,对预期结果与实际结果的比对已经做了规划。就是根据预期结果类型字段判断实际结果取什么样的值,并和预期结果以怎样的方式进行比对。本小节只需要将其实现即可,实现思路是需要先实现一个compare函数,无论预期结果类型和预期值是什么,都可以通过该compare函数匹配到对应的断言函数,然后再依次实现所有的断言函数。
在core\compare.py文件中添加断言函数,代码如下:
# chapter11\core\compare.py
import json
import os
import allure
from pathlib import Path
from json import JSONDecodeError
from requests import Response
def compare(response: Response, expect_type, expect_value):
"""通过 expect_type 字符串匹配对应的断言"""
type_map = {"status": status_compare,
"boolean": boolean,
"str_contain": str_contain,
"str_eql": str_eql,
"json_contain": json_contain,
"file_info": file_info,
}
compare_map = type_map.get(expect_type.lower(), "预期结果类型不存在")
return compare_map(response, expect_value)
def add_attachment(actual_body, expect_body, name="响应体",
attachment_type=allure.attachment_type.TEXT):
"""添加附件信息"""
allure.attach(body=str(actual_body), name=f"实际结果:{name}",
attachment_type=attachment_type)
allure.attach(body=str(expect_body), name=f"预期结果:{name}",
attachment_type=attachment_type)
def status_compare(response: Response, expect_value):
"""断言响应状态码与预期结果相等"""
status_code = response.status_code
add_attachment(status_code, expect_value, "响应状态码")
assert expect_value == status_code
def boolean(response: Response, expect_value):
"""断言布尔类型的响应体与预期结果相等"""
text = response.text
add_attachment(text, expect_value)
assert text.lower() == str(expect_value).lower()
def str_contain(response: Response, expect_value):
"""断言预期结果在响应体字符串中"""
text = response.text
add_attachment(text, expect_value)
assert expect_value in text
def str_eql(response: Response, expect_value):
"""断言预期结果与响应体字符串相等"""
text = response.text
add_attachment(text, expect_value)
assert expect_value == text
def json_contain(response: Response, expect_value):
"""断言JSON格式的响应体包含或相等预期结果"""
result_json = response.json()
try:
expect_value = json.loads(expect_value)
add_attachment(result_json, expect_value,
attachment_type=allure.attachment_type.JSON)
assert expect_value.items() <= result_json.items()
except JSONDecodeError as e:
add_attachment(e, expect_value, name="ERROR")
assert False
def file_info(response: Response, expect_value):
"""下载文件断言,断言文件大小大于0 和接口响应状态码是200"""
expect_value = json.loads(expect_value)
name = expect_value.get("name", 'download.txt')
file_name = f"{create_download_path()}/{name}"
__download_file(response, file_name)
file_size = __get_file_size(file_name)
add_attachment(file_size, "文件大小大于 0", "文件大小")
status_compare(response, 200)
assert file_size > 0
def create_download_path():
"""创建 download 文件夹"""
current_file = os.path.realpath(__file__)
download_path = Path(current_file).parent.parent.joinpath("download")
if not os.path.isdir(download_path):
os.makedirs(download_path)
return download_path
def __download_file(response: Response, file_name):
"""下载文件"""
f = open(file_name, 'ab')
for chunk in response.iter_content(chunk_size=512):
if chunk:
f.write(chunk)
f.close()
def __get_file_size(file):
"""获取文件大小"""
file_size = Path(file).stat().st_size
return file_size
首先,我们定义了一个compare函数,并在此函数下定义了一个字典,字典key对应预期结果类型,字典value对应断言函数。由此通过compare函数即可由预期结果类型找到对应的断言函数,断言函数对实际结果和预期结果进行比较,并给出是否通过的结论;然后定义了一个add_attachment函数,用于将预期结果和实际结果输出到Allure测试报告中,方便查看;之后就是依次实现不同类型的断言,具体如下:
·status_compare:断言响应状态码与预期结果相等。
· boolean:断言布尔类型的响应体与预期结果相等。
· str_contain:断言预期结果在响应体字符串中。
· str_eql:断言预期结果与响应体字符串相等。
· json_contain:断言JSON格式的响应体包含或相等预期结果。
· file_info:下载文件断言,断言文件大小大于0 和接口响应状态码是200。
对于文件下载的断言,还封装了两个私有函数__download_file和__get_file_size,__download_file用于下载文件,__get_file_size用于获取下载的文件大小。
11.6.8 添加测试用例函数
前面几节已经实现了测试用例文件、获取测试用例数据、发送接口请求和封装断言函数,本节将借助前几节已实现的功能实现测试用例函数。在core\test_case.py文件中添加如下:
# chapter11\core\test_case.py
import json
import pytest
import pandas as pd
import requests
from core.get_case import get_all_case, get_login_case, get_change_pwd_case
from common.read_config import login as login_conf
from core.send_request import send, set_session
from core.compare import compare
def case_ids_body(all_case):
"""获取所有的测试用例,并按照一定顺序排序,并返回测试用例数据和测试用例名"""
case_df_list = list(all_case)
case_df = pd.concat(case_df_list) if len(case_df_list) > 1 else case_df_list[0] #
合并所有的 sheet 用例
case_ids = case_df["用例编号"] + " " + case_df["用例名称"]
case_body = case_df[["接口地址", "请求方式", "请求头", "请求参数", "预期结果类型",
"预期结果"]]
return {"params": case_body.values, "ids": case_ids.values}
@pytest.fixture(**case_ids_body(get_all_case()))
def param_data(request):
"""参数化测试用例"""
# 返回 request 对象中 param,即参数化数据
return request.param
@pytest.fixture(**case_ids_body(get_login_case()))
def param_login_data(request):
"""参数化登录测试用例"""
return request.param
@pytest.fixture(**case_ids_body(get_change_pwd_case()))
def param_change_pwd_data(request):
"""参数化修改密码测试用例"""
return request.param
def run_case(case_fields, session=requests):
"""运行测试用例"""
url = case_fields[0]
method = case_fields[1]
headers = case_fields[2]
body = case_fields[3]
expect_type = case_fields[4]
expect_value = case_fields[5]
response = send(method, url, headers, body, session)
compare(response, expect_type, expect_value)
def test_case(param_data, session):
"""测试用例函数"""
run_case(param_data, session)
def test_login_case(param_login_data):
"""登录测试用例函数"""
run_case(param_login_data)
def test_change_pwd_case(param_change_pwd_data):
"""修改密码测试用例函数"""
pwd = eval(param_change_pwd_data[3]).get("old_password")
data = eval(login_conf["body"])
data["password"] = pwd
session = set_session(json.dumps(data))
run_case(param_change_pwd_data, session)
session.close()
在上面代码中首先定义了一个函数case_ids_body,用于获取所有的测试用例,并按照接口地址、请求方式、请求头、请求参数、预期结果类型、预期结果的顺序返回测试用例数据,以“用例编号 用例名称”的格式返回用例名称;然后通过param_data、param_login_data、param_change_pwd_data函数分别以参数化的形式传入所有测试用例数据、登录测试用例数据和修改密码测试用例数据;接着定义了一个基础函数run_case用于运行测试用例,从参数化数据中依次获取接口地址、请求方式、请求头、请求参数、预期结果类型、预期结果字段,并发送请求和结果断言;最后通过基础函数实现test_case(所有测试用例)、test_login_case(登录测试用例)、test_change_pwd_case(修改密码测试用例)测试用例函数。
下面对添加的代码进行测试,在core\test_case.py文件中添加pytest执行测试用例的代码,如下所示:
if __name__ == '__main__':
pytest.main(['-v', 'test_case.py'])
执行脚本core\test_case.py,控制台输出的结果如下所示:
============================= test session starts =============================
collecting ... collected 23 items
..\core\test_case.py::test_case[test_download_01 成功下载小程序包]
..\core\test_case.py::test_case[test_change_pwd_01 请求体中无旧密码]
..\core\test_case.py::test_case[test_change_pwd_02 请求体中无新密码]
..\core\test_case.py::test_case[test_change_pwd_03 请求体中无确认新密码]
..\core\test_case.py::test_case[test_change_pwd_04 旧密码为空]
..\core\test_case.py::test_case[test_change_pwd_05 新密码为空]
..\core\test_case.py::test_case[test_change_pwd_06 确认新密码为空]
..\core\test_case.py::test_case[test_change_pwd_07 旧密码错误]
..\core\test_case.py::test_case[test_change_pwd_08 新密码与确认新密码不一致]
..\core\test_case.py::test_case[test_cache_01 清理缓存成功]
..\core\test_case.py::test_login_case[test_login_01 成功登录]
..\core\test_case.py::test_login_case[test_login_02 请求体中无手机号]
..\core\test_case.py::test_login_case[test_login_03 请求体中无密码]
..\core\test_case.py::test_login_case[test_login_04 手机号为空]
..\core\test_case.py::test_login_case[test_login_05 密码为空]
..\core\test_case.py::test_login_case[test_login_06 手机号错误]
..\core\test_case.py::test_login_case[test_login_07 密码错误]
..\core\test_case.py::test_login_case[test_login_08 手机号不存在]
..\core\test_case.py::test_change_pwd_case[test_change_pwd_01 修改密码成功]
..\core\test_case.py::test_change_pwd_case[test_change_pwd_02 新密码长度=1]
..\core\test_case.py::test_change_pwd_case[test_change_pwd_03 新密码长度超长]
..\core\test_case.py::test_change_pwd_case[test_change_pwd_04 新密码含有空格]
..\core\test_case.py::test_change_pwd_case[test_change_pwd_05 修改密码成功_恢复旧密码]
============================= 23 passed in 11.77s =============================
从控制台输出的结果可以看到,一共收集了23条测试用例,并且全部执行通过,用时11.77秒。
至此,整个接口测试项目的核心内容已全部完成。
11.7 执行测试项目
接口测试项目核心内容已完成,接下来配置pytest执行参数和添加项目入口文件。
首先,在pytest.ini中添加pytest配置参数,改变pytest的默认行为。此文件内容根据需要设置,例如添加如下内容:
[pytest]
addopts = -sv
disable_test_id_escaping_and_forfeit_all_rights_to_community_support = True
addopts = -sv意味着pytest在执行时默认添加-vs参数,以输出更加详细的执行信息,并输出print函数的打印信息;disable_test_id_escaping_and_forfeit_all_rights_to_community_support=True可以使pytest在运行过程中保持中文utf-8格式编码。
然后在项目入口run.py文件中添加执行测试用例语句,再使用Allure命令生成测试报告,代码如下:
# chapter11\run.py
import os
import pytest
if __name__ == '__main__':
# 执行测试用例,并生成 Allure 测试报告 JSON 文件
pytest.main(['-s', '-v', './core/', '--alluredir', './report/json'])
# 执行 Allure 命令,将 JSON 文件转换为 XML 测试报告
os.system("allure generate ./report/json -o ./report/xml --clean")
执行脚本run.py,在download目录下可以看到下载的文件wxapp.zip,在report目录下可以看到生成的JSON文件和XML报告,如图11-5所示。
图11-5 下载文件和报告文件
打开report/xml/index.html文件查看测试报告,如图11-6所示。可以看到,一共运行了23条测试用例,通过率为100%。
图11-6 测试报告
打开的测试报告,在suites下可以看到每条测试用例运行的具体情况,如图11-7所示。
图11-7 查看用例详情
11.8 思 考 题
1. 什么是接口测试?
2. 为什么要实施接口测试?
3. 你都了解哪些接口协议?
4. 给你一个接口,你如何设计测试用例?
5. 测试一个接口的基本步骤有哪些?
6. 你之前做过的接口测试项目,测试流程是什么?