pytest中可以使用 @pytest.fixture 装饰器来装饰一个方法,被装饰的方法名可以作为一个参数传入到测试方法中。可以使用这种方式来完成测试之前的初始化,也可以返回数据给测试函数。
fixture的作用
- 定义传入测试中的数据集
- 配置测试前系统的初始状态
- 为批量测试提供数据源
fixture的用法
- 类似 setup,teardown 功能,但比它们更灵活
- 直接通过函数名调用或者使用装饰器 @pytest.mark.usefixtures('login')
- 允许使用多个 fixture
- 使用 autouse 自动应用,如果要返回值,需要传 fixture 函数
- 作用域(session > module > class > function)
应用-场景1:
- 测试用例执行时,有的测试用例需要登录,有的测试用例无需登录
- setup 和 teardown 无法满足
调用方式
- 测试用例中传入方法名
- @pytest.mark.usefixtures('login')
- 自动调用:@pytest.fixture(autouse=True)
代码如下:
import pytest
@pytest.fixture()
def login():
print("登录操作")
yield
print("注销操作")
@pytest.fixture()
def login3():
print("登录操作3")
yield
print("注销操作3")
def test_one(login):
print("用例one,执行前先登录")
def test_two():
print("用例two,无需登录")
# 可以叠加多个 @pytest.mark.usefixtures() ,先执行的放底层,后执行的放上层
@pytest.mark.usefixtures("login")
@pytest.mark.usefixtures("login3")
def test_three():
print("用例three,执行前先登录")
# 设置autouse=True 所有测试用例都会执行登录操作
# @pytest.fixture(autouse=True)
def login2():
print("登录操作2")
#
# def test_four():
# print("用例4,执行前先登录2")
# 在类声明上面加 @pytest.mark.usefixtures() ,
# 代表这个类里面所有测试用例都会调用该fixture
@pytest.mark.usefixtures("login")
class TestDemo:
def test_five(self):
print("用例5 需要登录操作")
def test_six(self):
print("用例6 需要登录操作")
应用-场景2:
- 整个模块有多条测试用例,执行全部用例前都需要打开浏览器;全部执行后需要关闭浏览器
- 打开和关闭浏览器的操作只需要执行一次(每次都执行打开操作,非常占用系统资源)
- 这种场景除了 setup_module/teardown_module 实现,还可以设置模块级别的 fixture 装饰器来实现。
作用域
- 控制方法:@pytest.fixture(scope="")
- scope的取值:
- function(默认) 函数或者方法级别都会被调用
- class 类级别的调用
- module 模块级别调用一次
- session 多个文件/包 调用一次(可以跨.py 文件调用,每个.py 文件就是module)
代码如下:
import pytest
@pytest.fixture(scope='module')
def open():
print("打开浏览器")
yield
print("关闭浏览器")
class TestSearch:
def test_search1(self,open):
print("test_search1")
# 要想引发异常,最简单的形式就是输入关键字raise,后跟要引发的异常的名称。
# 关键字raise是用来抛出异常的,一旦抛出异常后,后续的代码将无法运行。
raise NameError
class TestSearch2:
def test_search2(self,open):
print("test_search2")
@pytest.mark.usefixtures('open')
def test_search():
print("test_search3")
运行结果:
test_feature_scope.py::TestSearch::test_search1 打开浏览器
test_search1
FAILED
test_feature_scope.py::TestSearch2::test_search2 test_search2
PASSED
test_feature_scope.py::test_search test_search3
PASSED关闭浏览器
fixture 方法返回值的获取
- 在测试用例中使用 fixture 方法名 就可以获取到 yield 后面的返回值
- 具体代码见下面的代码
import pytest
@pytest.fixture(scope="class")
def login():
print("登录方法")
username = 'admin'
password = '123456'
yield [username,password]
print("退出登录")
class TestLogin:
def test_login(self,login):
print(f"用户的信息是:{login}")
print("这是一条 test_login 用例")
def test_login2(self,login):
print("这是一条 test_login2 用例")
运行结果:
test_login2.py::TestLogin::test_login 登录方法
用户的信息是:['admin', '123456']
这是一条 test_login 用例
PASSED
test_login2.py::TestLogin::test_login2 这是一条 test_login2 用例
PASSED退出登录
应用-场景3
- 多个测试开发工程师一起开发时,用到相同的功能
- 把公共模块放到方便大家调用的文件中,conftest.py 文件中
conftest.py 文件 使用规则
- conftest.py 这个文件名是固定的,不可以更改
- conftest.py 与运行用例在同一个包下,并且该包中有 __init__.py 文件
- 使用的时候不需要导入 conftest.py 文件, pytest 会自动识别到这个文件
- 所有同目录测试文件运行前都会执行 conftest.py 文件
- 放在项目的根目录下可以全局调用,放在 package 下,就在这个 package 下生效
- 全局的配置和前期的工作都可以写在这里
案例目录结构如下:
创建conftest.py 定义了公共方法,代码如下:
import pytest
@pytest.fixture(scope='session')
def connectDB():
print("sub demo下的 connectDB 连接")
yield
print("关闭数据库")
创建 test_demo2.py 文件,代码如下:
import pytest
# @pytest.fixture()
# def connectDB():
# print("test_demo2 下的 connectDB ")
def test_a(connectDB):
print("sub demo 下的 test_a")
class TestA():
def test_b(self):
print("sub demo 下的 test_b")
创建check_demo.py 文件,代码如下:
def test_demo(connectDB):
print("check demo 下的 test_demo方法")
def check_demo():
print("check demo 下的 check_demo方法")
class CheckDemo:
def check_demo2(self):
print("CheckDemo check_demo2")
执行结果如下:
test_demo2.py::test_a sub demo下的 connectDB 连接
sub demo 下的 test_a
PASSED
test_demo2.py::TestA::test_b sub demo 下的 test_b
PASSED
check_demo.py::test_demo check demo 下的 test_demo方法
PASSED
check_demo.py::check_demo check demo 下的 check_demo方法
PASSED
check_demo.py::CheckDemo::check_demo2 CheckDemo check_demo2
PASSED关闭数据库
fixture 传递参数
- 测试过程中需要大量的测试数据,如果每条测试数据都编写一条测试用例,用例数据是非常庞大的。
- 一般我们在测试过程中会将测试用例用到的数据以参数的形式传入到测试用例中,并为每条测试数据生成一个测试结果。
- 方法: @pytest.fixture(params=[1,2,3]),传入的数据是列表,传入的数据使用固定的参数名 request 来接收。
代码如下:
import pytest
@pytest.fixture(params=[1,2,3])
def login1(request):
data= request.param
print("获取数据")
return data
def test_case(login1):
print(login1)
print("测试用例1")
运行结果:
test_params.py::test_case[1] 获取数据
1
测试用例1
PASSED
test_params.py::test_case[2] 获取数据
2
测试用例1
PASSED
test_params.py::test_case[3] 获取数据
3
测试用例1
PASSED
pytest 配置
- 写在 pytest.ini 文件中
- 放在项目工程的根目录
- 不能用任何中文符号
[pytest]
markers 自定义 mark 标签名
addopts 运行时参数(可添加多个命令行参数,空格分隔,所有参数与命令行一致)
python_files 自定义测试文件命名规则
python_classes = Test_* 自定义测试类命名规则
python_functions= test_* check_* 自定义测试方法命名规则
testpaths = bilibili baidu 指定特定路径运行
norecursedirs = result logs datas test_demo* 运行时忽略某些文件夹
创建pytest.ini 文件,代码如下:
[pytest]
markers = bookstore
login
add
sub
addopts = -vs
;自定义测试文件命名规则
python_files = test_* check_*
;自定义测试类
python_classes = Test* Check*
;自定义测试方法
python_functions = test_* check_*
;指定特定路径:当在上级路径时,也会默认执行sub_demo
;testpaths = sub_demo
;忽略路径
norecursedirs = result logs datas
写在最后的话:小白同学愿意和大家一起成长~