Pytest+Allure实现用例管理与自定义测试报告
(一)快捷安装所需依赖
1、创建requirements.txt文件,里面填写我们项目所需的依赖
2、在Pycharm下方的Terminal栏中,输入命令pip install -r <文件名>
3、按回车键执行命令,即可自动下载我们项目所需要的依赖
4、然后输入pip list
,即可查看我们下载的依赖是否存在,存在则说明下载成功,如果失败的话,需要重新执行命令,有可能是网络波动导致下载依赖失败。
(二)Pytest测试用例规则
1、模块名必须要以test_
开头或者_test
结尾
2、测试类必须以Test
开头,并且不能有__init__方法
3、测试用例必须要以test_
开头
(三)Pytest的运行方式
- -n <数值>: 支持多线程或者分布试运行测试用例,大大提升运行时速度(前提是安装了pytest -xdist依赖)
- --reruns <数值>:任何用例失败后会进行重跑2次(前提是安装了pytest -rerunfailures依赖)
- -x:表示只要一个用例报错,那么测试停止
- --maxfail=<数值> :表示出现两个用例失败就停止
- -k: 根据测试用例的部分字符串指定测试用例,多个名称使用关键字“or”,“and”分隔
--html=<存放目录>: 运行完测试用例后生成测试报告(前提是安装了pytset -html)
1、主函数方式
一般会在目录的根目录下创建一个run.py文件,用来运行所有的测试用例,这个时候符合pytest测试用例规则的就会被发现并且运行,控制台输出一个.表示运行通过。
2、命令行方式
在pycharm下方的terminal中,输入
pytest
即可运行所有的符合测试用例规则的测试用例方法[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8RRZXlYn-1661627743675)(http://colablog.top/imagepytest-9.png)]。
(1)输入pytest
,运行所有测试用例
(2)输入pytest -vs <文件名>
,运行指定模块的测试用例
(3)输入pytest -vs <目录名>
,运行指定目录下的测试用例
(4)输入pytest -vs <具体用例方法>
,根据node id运行指定位置的测试用例
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IHduJ6ts-1661627743676)(http://colablog.top/imagepytest-9.png)]3、Pytest.ini配置文件运行
(1)在项目根目录下创建pytest.ini文件
(2)[pytest]
用于标记该文件为pytest配置文件
(3)addopts
表示命令行参数,多个参数使用空格分隔
(4)testpaths
表示测试用例搜索范围
(5)python_files
表示修改测试用例文件命名规则
(6)python_classes
表示修改测试用例类名命名规则
(7)python_functions
表示修改测试用例方法名命名规则
(8)markers
表示对用例进行分组,可按照不同的组来执行,此操作需要给测试用例方法添加装饰器,如模块名为“smoke”,测试用例需要添加@pytest.mark.smoke
来表示此用例属于该分组,addopts需要添加-m <分组名>,多个分组使用 '<分组名1> or <分组名2>',类似于-k参数。
📢ps:该文本不能存在中文字符!!!如果需要中文注释的话,需要使用Nodepad++打开,将编码格式修改为“GB2312”,在工具栏中的“编码-编码字符集-中文-GB2312”。
示例图:
(四)常用装饰器
@pytest.mark.run(order=1)
改变测试用例执行顺序,需要安装
pytest-ordering
插件。pytest默认执行顺序是从上到下,该装饰器可以修改测试用例的执行顺序,数值越小,优先级越高,有装饰器的比没有装饰器的优先级高。
@pytest.mark.skip(reason="")
@pytest.mark.skipif(<判断式>, reason="")
跳过不需要执行的测试用例,skip和skipif,skipif需要添加判断条件,如果成立则跳过该条测试用例,不成立则执行用例,skif只需要添加原因,直接不执行该条用例。
@pytest.fixture(scope="作用域",params="数据驱动",autouse="自动执行",ids="自定义参数名",name="别名")
scope参数 | 作用域 |
---|---|
function | 每条测试用例前后执行一次 |
class | 每个测试类前后执行一次 |
module | 在每个测试.py文件前后执行一次 |
package/session | 所有包前后执行一次 |
1、scope
function级别:
@pytest.fixture(scope="function")
def execute_sql():
print("········夹具前置")
yield
print("夹具后置········")
def test_baidu03(self, execute_sql):
print("这是一条测试用例")
assert 1 == 1
yield可以返回数值,并且可以在返回后继续执行后面的代码,而使用return则不可以
class级别:
@pytest.fixture(scope="class", autouse=True)
def execute_sql():
print("········夹具前置")
yield
print("夹具后置········")
pytest.mark.usefixtures("execute_sql")
class TestApi:
def test_baidu01(self):
print("这是一条测试用例")
assert 1 == 1
在测试类用需要添加夹具方法
module级别:
@pytest.fixture(scope="module", autouse=True)
def execute_sql():
print("········夹具前置")
yield
print("夹具后置········")
每个模块(.py文件)执行前后运行一次,效果类似于setup_module,teardown_module方法
2、autouser
代码示例:
@pytest.fixture(scope="function", autouse=True)
使用autouse,会自动给所有测试用例添加夹具
3、params
def read_data():
return ['1', '2', '3', '4']
@pytest.fixture(scope="function", params=read_data(), autouse=True)
def execute_sql(request):
print("········夹具前置")
yield request.param
print("夹具后置········")
@pytest.mark.newModel
def test_baidu04(self, execute_sql):
print(execute_sql)
print("这是一条测试用例04")
assert 1 == 1
定义一个返回值方法,然后传入params参数中,通过给夹具方法加上形参request(固定写法),然后通过yield返回request.param(固定写法)返回给测试用例,达到数据驱动的目的
4、ids
def read_data():
return ['1', '2', '3', '4']
@pytest.fixture(scope="function", params=read_data(), ids=['test1', 'test2', 'test3', 'test4'], autouse=True)
def execute_sql(request):
print("········夹具前置")
yield request.param
print("夹具后置········")
@pytest.mark.newModel
def test_baidu04(self, execute_sql):
print(execute_sql)
print("这是一条测试用例04")
assert 1 == 1
ids需要和params参数一起使用,ids是给params参数命名,ids参数的长度需要和params参数长度一致,一一对应,此作用是在执行用例时,能清晰看到每一条参数的名字,如下图所示。
5、name
@pytest.fixture(scope="function", params=read_data(), ids=['test1', 'test2', 'test3', 'test4'], autouse=True, name="fixture")
def execute_sql(request):
print("········夹具前置")
yield request.param
print("夹具后置········")
def test_baidu04(self, fixture):
print(fixture)
print("这是一条测试用例04")
assert 1 == 1
⚠️name用于给夹具起别名,测试用例的参数需要使用此别名,否则报错。
@pytest.mark.parametrize(参数名, 数据)
用于修饰测试用例方法,参数名表示数据的别名,数据参数可放数组、字典、列表、元组等,使用此装饰器的测试用例方法形参中,必须添加此装饰器的参数名且一致,才能实现数据驱动。
@pytest.mark.parametrize("name, test_id", [["哈哈哈", 1], ["呵呵呵", 2], ["嘻嘻嘻", 3]])
def test_baidu(self, name, test_id)
结合Yaml,实现数据驱动,接口自动化
def read_yaml(path):
with open(path, mode="r", encoding="utf-8") as file:
data = yaml.load(file, Loader=yaml.FullLoader)
return data
@allure.epic("项目名称:若依管理系统")
@allure.feature("模块名称:登录模块")
class TestApi:
@allure.story("登录模块")
@allure.severity(allure.severity_level.BLOCKER)
@pytest.mark.run(order=2)
@pytest.mark.smoke
@pytest.mark.parametrize("data", read_yaml("./testcases/API/data.yaml"))
def test_baidu01(self, data):
allure.dynamic.title(data["name"])
allure.dynamic.description(data["description"])
res = requests.request(method=data["request"]["method"], url=data["request"]["url"], json=data["request"]["data"])
assert res.json()["msg"] == "操作成功"
📢PS:读取Yaml文件的路径哪个文件调用就相对于哪个文件的相对路径,因此,建议使用一个工具类进行绝对路径拼接。
(五)Pytest前后置器
1、测试用例执行之前运行
def setup(self)
2、测试用例执行之后运行
def teardown(self)
3、测试类执行之前运行
def setup_class(self)
📢ps:常用于创建数据库连接、日志对象等只需要执行一次的操作
4、测试类执行之后运行
def teardown_class(self)
5、模块执行之前运行
def setup_module()
6、模块执行之后运行
def teardown_module()
📢PS:前后置器只能作用于全局,不能指定测试用例,所以出现了更强大的装饰器—@pytest.fixture
(六)conftest共享夹具
📢:配合@pytest.fixture(scope="package/session")
一起使用。在每一个模块文件里创建夹具,只能内部使用,无法达到共享,于是产生了conftest.py
import pytest
def read_data():
return ['1', '2', '3', '4']
@pytest.fixture(scope="package", params=read_data(), ids=['test1', 'test2', 'test3', 'test4'], autouse=True, name="fixture")
def execute_sql(request):
print("········夹具前置")
yield request.param
print("夹具后置········")
, 在测试用例包下创建conftest(固定名称),在其中添加夹具方法,在所有测试用例文件中,都能使用到此夹具,目录级别越高的conftest会优先执行,目录级别比较低的conftest会后面执行。当conftest的scope级别和用例文件内setup、teardown方法级别一致时,conftest会优先执行。
(七)Assert断言
- assert xx:判断 xx 为真
- assert not xx :判断 xx 不为真
- assert a in b:判断 b 包含 a
- assert a == b :判断 a 等于 b
- assert a != b :判断 a 不等于 b
(八)Allure报告
1、Allure下载
官网下载Allure,官网地址: Allure2.18.1下载
2、配置
解压文件后,将bin目录添加到系统变量path中
3、验证
命令行窗口中验证Allure是否安装成功
allure --version
4、安装allure-pytest
检验Pycharm中是否安装allure-pytest
pip list
5、配置pytest.ini
在pytest.ini中添加运行参数
[pytest]
addopts = -vs -m 'smoke or newModel' --alluredir=reports/temps --clean-alluredir
--alluredir=DIR:指定allure报告所需json数据的文件夹;
--clean-alluredir:如果已经存在报告,那就先清空,然后再生成新的测试报告;
6、定制Logo
- 进入E:\Allure\allure-2.18.1\config目录下
- 修改allure.yml,末尾添加
- custom-logo-plugin
- 进入E:\Allure\allure-2.18.1\plugins\custom-logo-plugin\static,将定制logo图标复制至此
修改style.css,将内容修改为以下代码
.side-nav__brand { background: url('logo.png') no-repeat left center !important; margin-left: 40px; height: 90px; background-size: contain !important; } .side-nav__brand-text{ display: none; }
- 最终展示效果
7、生成报告
在run.py中生成测试报告
pytest.main()
os.system("allure generate reports/temps -o reports/allures --clean")
使用os.system()调用cmd命令行模式,allure generate <allure-result中间文件> -o 输出目录(默认路径:allure report)- -clean 清缓存。
8、报告优化
@allure.epic()
项目名称,修饰类
@allure.feature()
模块名称,修饰类
@allure.story()
子功能名称,修饰方法
@allure.title()
测试用例名称,修饰方法,适用于单个用例单个名称
allure.dynamic.title()
测试用例名称,方法体内使用,适用于动态参数的测试用例,可结合
@pytest.mark.parametrize
一起使用,当@allure.title()和此方法一同使用时,此方法会覆盖掉@allure.title()
@allure.epic("项目名称:若依管理系统")
@allure.feature("模块名称:登录模块")
class TestApi:
@allure.story("登录模块")
@allure.severity(allure.severity_level.BLOCKER)
@pytest.mark.run(order=2)
@pytest.mark.smoke
@pytest.mark.parametrize("data", read_yaml("./testcases/API/data.yaml"))
def test_baidu01(self, data):
allure.dynamic.title(data["name"])
allure.dynamic.description(data["description"])
res = requests.request(method=data["request"]["method"], url=data["request"]["url"], json=data["request"]["data"])
assert res.json()["msg"] == "操作成功"
@allure.severity()
参数名 | 参数级别 | 对标测试用例级别 |
---|---|---|
allure.severity_level.BLOCKER | 最高,阻塞级 | 致命:系统崩溃,内存泄漏等 |
allure.severity_level.CRITICAL | 次高,严重级 | 严重:功能未实现,服务器错误等 |
allure.severity_level.NORMAL | 一般,正常级 | 一般:逻辑错误,功能报错等 |
allure.severity_level.MINOR | 较低,轻微级 | 轻微:条件查询错误,界面UI错误 |
allure.severity_level.TRIVIAL | 最低,建议级 | 建议:错别字,样式优化等 |
此装饰器,可以修饰测试方法,也可以修饰测试类,都是用于表现不同程度的级别
@allure.description()
修改测试用例描述信息,用于装饰测试方法,适用于单个用例
allure.dynamic.description()
修改用例描述信息,用于测试方法内部,适用于动态参数的测试用例,可结合
@pytest.mark.parametrize
一起使用,当allure.description()和此方法一同使用时,此方法会覆盖掉allure.description()
@allure.epic("项目名称:若依管理系统")
@allure.feature("模块名称:登录模块")
class TestApi:
@allure.story("登录模块")
@allure.severity(allure.severity_level.BLOCKER)
@pytest.mark.run(order=2)
@pytest.mark.smoke
@pytest.mark.parametrize("data", read_yaml("./testcases/API/data.yaml"))
def test_baidu01(self, data):
allure.dynamic.title(data["name"])
allure.dynamic.description(data["description"])
res = requests.request(method=data["request"]["method"], url=data["request"]["url"], json=data["request"]["data"])
assert res.json()["msg"] == "操作成功"
@allure.link(name="", url="")
接口地址,name表示接口地址名称,URL表示链接地址
@allure.issue(name="", url="")
BUG地址,name表示BUG地址名称,URL表示BUG链接地址
@allure.testcase(name="", url="")
测试用例地址,name表示测试用例名称,URL表示测试用例链接地址
@allure.step(title="")
测试步骤,用于修饰方法,当该方法被测试用例调用时,会显示为测试步骤,该方法可传参
with allure.step
测试步骤,用于方法内部,会显示为测试步骤,该方法用于方法内部,无法传参
@allure.epic("测试项目:百度")
@allure.feature("模块名称:百度搜索")
class TestApi:
@allure.story("百度搜索框")
@allure.title("能否根据关键词搜索正确结果")
@allure.description("验证百度模块")
@pytest.mark.smoke
@allure.link(name="接口地址", url="https://www.colablog.top")
@allure.issue(name="BUG地址", url="https://www.colablog.top")
@allure.testcase(name="测试用例地址", url="https://www.colablog.top")
def test_baidu(self):
with allure.step("打开浏览器"):
print("打开谷歌浏览器")
with allure.step("输入关键词"):
print("输入搜索的关键词")
with allure.step("搜索查看结果"):
print("搜索结果并查看是否正确")
allure.dynamic.title("覆盖-能否根据关键词搜索正确结果")
allure.dynamic.description("覆盖-验证百度模块")
print("这是第二条测试用例")
assert 1 == 1
allure.attach
🔊附件,用法方法内部,测试报告会显示附件,需要配合with open使用
with open("D:\\壁纸.jpg", mode="rb") as f:
allure.attach(body=f.read(), name="错误截图", attachment_type=allure.attachment_type.JPG)