文章目录:
- 一、前言
- 二、pytest固件
-
- 1、什么是固件
-
- [1.1 unittest风格的固件](#1.1 unittest风格的固件)
-
- [1.1.1 每一个测试用例(方法)加前后置](#1.1.1 每一个测试用例(方法)加前后置)
- [1.1.2 每一个测试类加前后置](#1.1.2 每一个测试类加前后置)
- 2、pytest固件风格
-
- [2.1 使用fixture实现前后置](#2.1 使用fixture实现前后置)
- [2.2 fixture的介绍](#2.2 fixture的介绍)
-
- [2.2.1 fixture的源码](#2.2.1 fixture的源码)
- [2.2.2 fixture的参数化:params](#2.2.2 fixture的参数化:params)
- [2.2.3 fixture自动使用:autouse](#2.2.3 fixture自动使用:autouse)
- [2.2.4 fixture动态名称:name](#2.2.4 fixture动态名称:name)
- [2.3 fixture的跨文件使用](#2.3 fixture的跨文件使用)
- [三、conftest 统一管理fixture](#三、conftest 统一管理fixture)
- 四、插件中的fixture
- 五、pytest的运行过程
- 六、Allure的企业级测试报告
-
- 1、搭建Allure的环境配置
- 2、生成Allure的测试报告
- 3、Allure的常用命令
- 4、Allure报告的企业级定制
-
- [4.1 logo定制](#4.1 logo定制)
-
- [4.1.1 启用插件](#4.1.1 启用插件)
- [4.1.2 配置插件](#4.1.2 配置插件)
- [4.2 内容定制](#4.2 内容定制)
一、前言
本文带你系统掌握Pytest的两大核心能力:固件(fixture)与Allure报告。从unittest风格的前后置开始,逐步深入到fixture的灵活用法、conftest统一管理、插件集成,再到企业级Allure报告的搭建与定制。内容实用、节奏友好,帮你轻松写出更规范的自动化测试框架。
二、pytest固件
1、什么是固件
我们当初在学习jmeter接口测试的时候,我们可以在取样器的执行之前和取样器的执行之后做一些事情。
那么这个固件大概就是这样的一个意思,我们更多的名字是叫他前后置:在我们的pytest中我们测试用例的执行之前以及测试用例的执行之后自动执行的方法
1.1 unittest风格的固件
1.1.1 每一个测试用例(方法)加前后置
现在我们有这样的一个需求:
需要在一个在每一个测试用例之前和每一个测试用例执行之后去自动执行一些方法
代码:
py
class Test:
# 第一个方法(第一个测试用例)
def test_a(self):
assert 1 == 1
# 第二个方法(第二个测试用例)
def test_b(self):
assert 1 == 2
那么现在我要写一个main文件,里面调用pytest的main方法,就可以启动pytest框架

那么接下在我们项目的根目录下新建一个配置文件pytest.ini,那么这样之后,我们就可以在配置文件中输入我们的pytest执行参数,这样我们就在启动main方法的时候,启动pytest,然后pytest就会读取这个配置文件,然后将执行的时候就会带上里面的参数,比如-v 那么就会打印出详细的信息
py
# 节[pytest]:使用pytest的时候会读取这个配置文件
[pytest]
# 配置命令行参数 -v 就是我们在执行的时候 实际是 pytest -v 他会打印出比较详细的信息
addopts = -vs
OK,此时我们去执行

执行结果如我们所料:目前没有前后置

上述有两个测试用例,OK,现在我们会在每一个测试用例之前添加前后置(所谓的前后置就是执行这个用例之前要自己做的事情,以及执行完这个用例之后要做的事情)
unittest 方法级别前置处理器:setup_method
unittest 方法级别后置处理器:teardown_method
py
class Test:
# 方法级别的前置处理器
def setup_method(self):
print('每一个测试用例执行之前自动执行')
# 方法级别的后置处理器
def teardown_method(self):
print('每一个测试用例执行之后自动执行')
# 第一个方法(第一个测试用例)
def test_a(self):
assert 1 == 1
# 第二个方法(第二个测试用例)
def test_b(self):
assert 1 == 2
ok,那么此时你就看结果:

1.1.2 每一个测试类加前后置
unittest 类级别前置处理器:setup_class
unittest 累级别后置处理器:teardown_class
代码:
py
class Test:
# 类级别的前置处理器
def setup_class(self):
print('本类所有测试用例执行之前自动执行')
# 类级别的后置处理器
def teardown_class(self):
print('本类所有测试用例执行之后自动执行')
# 第一个方法(第一个测试用例)
def test_a(self):
assert 1 == 1
# 第二个方法(第二个测试用例)
def test_b(self):
assert 1 == 2

ok,但是我们自动化测试框架主要使用pytest,所以接下来我们就介绍pytest中的前后置。👇
2、pytest固件风格
注意固件就是我们是前置处理器和后置处理器,说白了就是前置方法和后置方法。
我们的pytest中固件的类型会比我们的unittest多一些。
2.1 使用fixture实现前后置
首先来认识两个单词:fixture译为固定装置;yield译为产量

那么我们在pytest中是使用装饰器来实现前后置的,首先我们定义一个函数,然后给这个函数加上@pytest.fixture()此时就给这个函数赋予了新的功能,于是我们就在这个函数体中写前置代码和后置代码,前后置代码使用yield分开。
py
import pytest
@pytest.fixture()
def test_tan():
# yield之前写前置代码
print('用例之前执行')
yield
# yield之后写后置代码
print('用例之后执行')
然后怎么使用呢?我们直接将test_tan这个方法引用作为参数,传递到你需要执行我们的测试用例的参数中就行
py
import pytest
@pytest.fixture()
def test_tan():
# yield之前写前置代码
print('用例之前执行')
yield
# yield之后写后置代码
print('用例之后执行')
class Test:
# 第一个方法(第一个测试用例)
def test_a(self, test_tan):
assert 1 == 1
# 第二个方法(第二个测试用例)
def test_b(self):
assert 1 == 2

此时的结果如下:

我们了解了上述的一个流程,我们可以知道,我们的pytest不仅能够完美的代替unittest而且代码更加简洁方便灵活,具有Python语法特色。
可以知道:
pytest的固件风格:fixture
他的创建方式:
- 是一个函数
- 使用装饰器来创建的
@pytest.fixture() - 使用一个生成器的关键字
yield
2.2 fixture的介绍
2.2.1 fixture的源码
py
def fixture(
fixture_function: FixtureFunction | None = None,
*,
scope: _ScopeName | Callable[[str, Config], _ScopeName] = "function",
params: Iterable[object] | None = None,
autouse: bool = False,
ids: Sequence[object | None] | Callable[[Any], object | None] | None = None,
name: str | None = None,
) -> FixtureFunctionMarker | FixtureFunctionDefinition:
fixture这个方法里面有以下几个主要参数,他是一个装饰器,装饰器就是函数。
- fixture_function:被装饰的函数
- scope:共享范围级别主要有以下几个属性:默认情况下是function级别
- function:函数范围内都可以共享前后置(最小的级别,一个函数就是一个测试用例)
- class:类级范围内都可以共享前后置
- module:模块范围内都可以共享前后置
- package:包范围内都可以共享前后置
- session: 全局范围内都可以共享前后置
- params、ids:fixture的参数化(2.2.2有介绍)
- autouse:fixture的自动使用(2.2.3有介绍)
- name:fixture的动态名称(2.2.4有介绍)
比如:
py
@pytest.fixture(scope='class') # 修改为类级别
def test_tan():
# yield之前写前置代码
print('用例之前执行')
yield
# yield之后写后置代码
print('用例之后执行')
class Test:
# 第一个方法(第一个测试用例)
def test_a(self, test_tan):
assert 1 == 1
# 第二个方法(第二个测试用例)
def test_b(self, test_tan):
assert 1 == 2

2.2.2 fixture的参数化:params
此处我们就了解即可,后续我们也会很少使用的,因为他会让用例也被动的进行了参数化,那么具体的效果如下:
py
import pytest
@pytest.fixture(params=[1, 2, 3])
def test_tan():
print('前置')
yield
print('后置')
def test_abc(test_tan):
print(123)
结果:

2.2.3 fixture自动使用:autouse
什么意思呢?你使用fixture,那么你就得调用,如下所示:

那么现在问题来了:如果我有几千个函数,此时你是不是得每一个方法都要写这个函数参数呢?或者说我以后不用fixture修饰的test_tan方法的时候,那么以后我是不是就要一个一个方法的将以前调用的test_tan取出呢?这个不灵活。
所以为了我们的开闭原则我们就引入了autouse,如下:
py
import pytest
@pytest.fixture(autouse = True)
def test_tan():
print('前置')
yield
print('后置')
def test_abc():
print(123)

2.2.4 fixture动态名称:name
其实这个动态名称就是我们的重命名,具体例子如下所示:
py
import pytest
@pytest.fixture(name='lisi') # 使用name参数进行重命名
def test_tan():
print('前置')
# 注意:如果只想要我们是前置,那么就不需要有yield
# yield
# print('后置')
def test_abc(lisi): # 此处使用的是重命名之后的函数引用
print(123)

2.3 fixture的跨文件使用
首先看以下这个例子:
我们在test_a.py中的fixture在test_b.py中不允许被使用

其实这个原因是:我们的Python文件之间天然是有隔离的,他们a文件中的东西,如果不做特殊处理,是不允许被b文件使用的。
那么此时怎么办呢?怎么解决呢?
其实我们的思路很简单,我们可以对fixture进行统一的管理,这个就是我们接下来要引出的知识点👇
三、conftest 统一管理fixture
现在的问题是:我在test_a文件中写的fixture在我们的test_b文件中使用不了,此时我们的解决办法就是找一个全局文件,文件的名字必须是conftest.py。👇

py
import pytest
@pytest.fixture(scope='session', autouse=True)
def test_tan():
print('前置')
yield
print('后置')

如此一来,我们的test_a.py文件中的测试用例和我们的test_b.py文件中的测试用例都可以共享我们的fixture了。👇

注意事项:
conftest.py文件名是固定的,不能修改也不能写错- conftest.py中的fixture会自动的被pytest加载,会自动的被任意一个文件中的用例使用。
- conftest.py并不是全局唯一的,他可以在每一个目录中创建,他遵循就近原则,什么意思呢?就是如果你的conftest.py中有我们的fixture,此时我们如果在test_a.py中也写了fixture,那么他就会遵循就近原则使用我们test_a.py文件中的fixture;还有一个种情况就是,如果子目录中有conftest.py那么他就会就近的找这个文件;即当前文件、同目录conftest、上级目录conftest、上上级目录conftest,例子如下:


用例在执行的时候就近执行的优先级更高,可能会混乱,所以为了简单起见,我们就做一个小小的约定:所有的fixture 定义在根目录的conftest中,如此一来就可以统一的实现管理了。
四、插件中的fixture
我们上述介绍的fixture是pytest自带的框架的fixture,那么其实我们有些插件也是带了fixture的👇
pytest-base-url这个插件,提供了一个base_url的fixture,这个fixture的内容可以进行灵活的设置,fixture他的用法相对简单和固定。
怎么使用?
首先,如果你没有安装我们的pytest-base-url插件,那么就使用pip install pytest-base-url进行安装。
你安装完毕之后,那么此时我们这个插件就自己带有一个fixture,大概源码是如下所示的:
py
@pytest.fixture(scope="session")
def base_url(config):
"""
Fixture which returns the base URL used for functional testing of
a web application or service.
"""
try:
return config.option.base_url
except AttributeError:
return config.getoption("--base-url")
那么你想使用这个fixture,就只需要将这个base_url引用传递给你需要的测试用例即可。
接下来看这个base_url的方法有返回值,他的返回值是:把配置文件中的base_url内容,动态的传递到用例中。
比如我们的配置文件如下:
py
# 节[pytest]:使用pytest的时候会读取这个配置文件
[pytest]
# 配置命令行参数 -v 就是我们在执行的时候 实际是 pytest -v 他会打印出比较详细的信息
addopts = -vs
# 注册标记:按行,一行一个标记
markers =
smoke:冒烟测试
usermanger:用户管理
api:接口用例
ut:单元测试用例
base_url = https://www.baidu.com
然后我们就使用这个fixture:
py
class Test:
def test_a(self, base_url):
# 打印我们的base_url
print(base_url)

从本质上看,我们的pytest-base-url的插件实现了一种效果:把配置文件中的内容,动态的传递到用例中。
五、pytest的运行过程
我们pytest的运行过程:
- 启动框架:调用pytest中的main方法或者就是直接使用我们的命令行
pytest 参数 - pytest去读取我们的配置文件
pytest.ini里面有我们的参数、标记、配置。 - 加载我们的
conftest.py文件中的fixture和hook - 加载我们的
test_*.py文件中的测试用例
六、Allure的企业级测试报告
我们上一期介绍了一个测试报告:就是我们的pytest-html,但是这个插件生成的测试报告不好看,所以我们本期就介绍Allure企业级测试报告。
Allure是一个测试报告框架,他可以去生成精美的、企业级的、可扩展的html测试报告。
同时Allure是一个Java程序,所以意味着我们使用Allure的时候得有Java运行环境。
我们Allure的学习目标:
- 搭建Allure的运行环境
- 生成Allure的测试报告
- Allure的常用命令
- Allure报告的企业级定制
1、搭建Allure的环境配置
- 首先得有JDK,他是Java的运行环境
- 安装Allure:
- 下载
https://github.com/allure-framework/allure2/releases - 选择压缩包下载,然后解压
- 配置环境变量:将\bin所在的目录路径设置环境变量
- 验证:allure

- 下载
Allure详细的环境配置可参考博客:看Allure环境配置部分即可
2、生成Allure的测试报告
生成Allure的测试报告的过程:
- 执行用例(使用我们的pytest框架来执行用例)
- 生成Allure报告的执行数据(这个功能使用allure-pytest插件完成)
- 生成Allure的测试报告(这个功能由我们的Allure完成)
总结 :pytest---->allure-pytest---->allure
OK,那么知道执行过程之后,我们怎么去实现呢?
第一点,你要知道,我们执行测试用例使用的是pytest框架,那么启动这个框架有多种方式,其中我们使用的是调用pytest中main方法的方式,那么一旦启动这个main方法之后,他就会去读取我们的配置文件,此时我们会在配置文件中将我们pytest执行的参数配置上。
py
# 节[pytest]:使用pytest的时候会读取这个配置文件
[pytest]
# -vs: -v代表的是我们执行结果会输出比较详细的信息 -s代表的是允许我们的输入输出执行
addopts = -vs
然后我们怎么去生成allure测试报告的执行数据呢?就是说,你pytest执行测试用例之后,有结果数据,我怎么将这个结果数据变为allure看得懂的数据呢?首先你得有allure-pytest插件,然后我们得在配置文件中为我们的pytest框架执行的时候,配置一个命令行参数--alluredir=./temps 和 --clean-alluredir代表的是,我将测试用例执行之后的结果数据变为allure看得懂的执行数据之后,我将这个allure报告的执行数据存在我们的temps目录下,同时如果该目录下有我们的原来数据,那么我们就得将数据删除,从而得到我们的全新的干净的数据。
py
# 节[pytest]:使用pytest的时候会读取这个配置文件
[pytest]
# -vs: -v代表的是我们执行结果会输出比较详细的信息 -s代表的是允许我们的输入输出执行
# --alluredir=./temps : allure的执行数据的目录
# --clean-alluredir: 将allure的执行数据的目录中旧数据删除
addopts = -vs --alluredir=./temps --clean-alluredir
最后我们生成allure报告怎么做呢?
你看,我们说启动框架我们使用的是pytest中的main方法:pytest.main()那么这一行代码,经过我们上述的配置文件的配置之后,他做了这些事:加载插件+执行用例+得到allure报告的执行数据。现在我们已经有allure数据了,那么现在我们唯一却的就是这么生成allure报告了,怎么做呢?
我们导入 os 模块,让他执行我们的系统命令:os.system("allure generate ./temps -o ./reporst --clean") 使用这个os好比就是你在cmd中直接操作一样的
完成之前两步的代码
py
import pytest # 导入我们的pytest
pytest.main() # 启动我们的测试框架: 加载插件、执行测试用例、生成allure报告
生成allure报告的代码
py
import pytest # 导入我们的pytest
import os # 导入我们的os可以执行系统命令,好比就在我们的cmd操作一样
pytest.main() # 启动我们的测试框架: 加载插件、执行测试用例、生成allure报告
os.system("allure generate ./temps -o ./reports --clean") # 根据数据生成报告

allure
- 调用 allure 命令行工具
- 作用:生成 Allure 测试报告
generate
- 固定子命令
- 意思:生成报告
./temps
./= 当前目录temps= 存放 allure 临时 json 数据的文件夹 这个要和什么上述配置文件中配置的allure的执行数据路径要一致。- 意思: 从 ./temps 文件夹读取测试结果数据
-o ./reports
-o= output 输出目录./reports= 要把 最终 HTML 报告生成到这个文件夹- 意思: 把报告输出到 ./reports 文件夹
--clean
- 意思:生成前清空旧的报告文件夹
- 不加的话,旧报告不会删,可能冲突
- 加了最安全
最后,我们就直接运行即可:



报告结果:

如何切换语言为中文:


3、Allure的常用命令
allure generate:生成报告allure serve:展示报告allure open:打开已存在的报告allure plugin:展示插件
注意:serve = generate + open
4、Allure报告的企业级定制
4.1 logo定制
4.1.1 启用插件
你要定制自己的loge,那么你的allure框架得有这个logo插件
首先我们使用allure plugin 看一下有哪些插件,到底有没有我们想要的自定义logo的插件:

里面没有我们的自定义logo插件,那么此时我们就怎么办呢?我们就吧这个插件加上:
找到我们的allure安装目录中的\plugins目录:
复制我们的自定义插件名:custom-logo-plugin

接下来在我们的allure安装目录下有一个config目录下打开config.yml

在最后添加插件
yml
plugins:
- junit-xml-plugin
- xunit-xml-plugin
- trx-plugin
- behaviors-plugin
- packages-plugin
- screen-diff-plugin
- xctest-plugin
- jira-plugin
- xray-plugin
- custom-logo-plugin # 新增自定义插件

此时我们就重新运行我们的测试用例,然后打开测试报告

4.1.2 配置插件
那么我们打开自定义插件的目录:

他里面就一个logo文件和一个css样式文件,所以我们自定义的时候就做两件事情:
创建一个logo的文件

修改CSS文件 :将css修改为如下所示
css
.side-nav__brand{
background: url('tan.jpg') no-repeat left center !important;
margin-left: 22px;
height: 90px;
background-size: contain !important;
}
.side-nav__brand-text{
display: none;
}
.node__parameters {
display: none;
}

4.2 内容定制
内容定制的本质:是对执行结果惊喜标注和分组
内容定制的方式:通过装饰器来添加不同的标注内容
标注的内容具体有以下几种:
- epic:史诗,也就是项目名称
- feature:特性,也就是我们模块的名称
- story:故事,也就是我们场景、接口的名称
- title:标题,也就是我们用例的名称
装饰器:@allure.上述的标注内容("描述")
步骤:with allure.step("步骤描述")
例如:
py
import allure
# 轻聊 - 用户管理模块 - 登录接口 成功用例
@allure.epic("[项目名称]轻聊")
@allure.feature("[模块名称]用户管理模块")
@allure.story("[接口名称]登录接口")
@allure.title("[用例名称]登录用例-成功")
def test_login_success():
# 登录接口测试步骤
with allure.step("1. 构造登录请求参数"):
pass # 写具体步骤的代码
with allure.step("2. 发送登录接口请求"):
pass # 写具体步骤的代码
with allure.step("3. 校验登录返回结果"):
pass # 写具体步骤的代码
# 企悦抽 - 奖品管理模块 - 上传奖品 成功用例
@allure.epic("[项目名称]企悦抽")
@allure.feature("[模块名称]奖品管理模块")
@allure.story("[接口名称]上传奖品接口")
@allure.title("[用例名称]上传奖品用例-成功")
def test_upload_prize_success():
with allure.step("1. 启动浏览器"):
pass # 写具体步骤的代码
with allure.step("2. 登录账号"):
pass # 写具体步骤的代码
with allure.step("3. 进入奖品管理界面"):
pass # 写具体步骤的代码
with allure.step("4. 上传奖品数据"):
# 正常测试用例,断言改为正确逻辑
assert True
此时执行测试用例之后,打开我们的报告
然后在我们的【功能】中打开:
首先我们的分组就很明确:

其次我们的用例执行的步骤也体现出来了

OK,那么老铁们,我们本期就到这里就结束了,笔记整理不易,如果对你有帮助,点赞👍加关注,我们下期见~