书籍来源:房荔枝 梁丽丽《pytest框架与自动化测试应用》
一边学习一边整理老师的课程内容及实验笔记,并与大家分享,侵权即删,谢谢支持!
附上汇总贴:pytest框架进阶自学系列 | 汇总_热爱编程的通信人的博客-CSDN博客
常用使用场景:相互依赖的fixture可能是多个,或提前准备的数据也可能是多个,或数据准备也可能有依赖和先后。
并列使用fixture
常用使用场景:前提条件有多个,可以并列使用多个fixture,多个@注解,在一个测试方法中使用时将传入的参数写多个就能解决此问题。
具体执行步骤:
(1)新建文件test_fixture_twofixtures-1.py。
(2)创建open_browser()函数。在函数上添加@pytest.fixture(scope="module")。
(3)创建login()函数。在函数上添加@pytest.fixture()。
(4)创建3个测试函数。在第一个测试函数test_soso的参数中传入open_browser、login的函数名,表示调用这两个函数。
代码如下:
import pytest
@pytest.fixture()
def login():
print("这是个登录模块!")
@pytest.fixture(scope="module")
def open_browser():
print('打开首页!')
def test_soso(open_browser, login):
print('case1:登录后执行搜索')
def test_cakan():
print('case2:不登录就看')
def test_cart(login):
print('case3:登录,加购物车')
执行结果如下,由于open_browser函数上的fixture的层级范围是module,所以先执行open_browser,也就是最先执行的是测试方法test_soso,读到输入参数时先执行open_browser,然后执行login方法,再执行test_soso中的代码。之后正常执行test_cakan和test_cart方法,执行test_cart方法时先执行login方法。
D:\SynologyDrive\CodeLearning\WIN\pytest-book\venv\Scripts\python.exe "C:/Program Files/JetBrains/PyCharm Community Edition 2022.3.2/plugins/python-ce/helpers/pycharm/_jb_pytest_runner.py" --path D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-3\test_fixture_twofixtures.py
Testing started at 10:33 ...
Launching pytest with arguments D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-3\test_fixture_twofixtures.py in D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-3
============================= test session starts =============================
platform win32 -- Python 3.7.7, pytest-5.4.1, py-1.11.0, pluggy-0.13.1 -- D:\SynologyDrive\CodeLearning\WIN\pytest-book\venv\Scripts\python.exe
cachedir: .pytest_cache
rootdir: D:\SynologyDrive\CodeLearning\WIN\pytest-book
collecting ... collected 3 items
test_fixture_twofixtures.py::test_soso
test_fixture_twofixtures.py::test_cakan
test_fixture_twofixtures.py::test_cart
============================== 3 passed in 0.21s ==============================
Process finished with exit code 0
打开首页!
这是个登录模块!
PASSED [ 33%]case1:登录后执行搜索
PASSED [ 66%]case2:不登录就看
这是个登录模块!
PASSED [100%]case3:登录,加购物车
嵌套调用fixture
常用使用场景:有先后关系的嵌套调用,多个@注解依赖可以通过传参方式,测试方法调用时只需调用最后的那种方法fixture,然后调用其他的fixture。
在上面代码的基础上复制并修改,将login函数的参数中加入open_browser,所以下面的测试方法就不用添加open_browser了。
代码如下:
import pytest
@pytest.fixture()
def login(open_browser):
print("这是个登录模块!")
@pytest.fixture(scope="module")
def open_browser():
print('打开首页!')
def test_soso(login):
print('case1:登录后执行搜索')
def test_cakan():
print('case2:不登录就看')
def test_cart(login):
print('case3:登录,加购物车')
执行结果如下:
D:\SynologyDrive\CodeLearning\WIN\pytest-book\venv\Scripts\python.exe "C:/Program Files/JetBrains/PyCharm Community Edition 2022.3.2/plugins/python-ce/helpers/pycharm/_jb_pytest_runner.py" --path D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-3\test_fixture_twofixtures-2.py
Testing started at 10:43 ...
Launching pytest with arguments D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-3\test_fixture_twofixtures-2.py in D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-3
============================= test session starts =============================
platform win32 -- Python 3.7.7, pytest-5.4.1, py-1.11.0, pluggy-0.13.1 -- D:\SynologyDrive\CodeLearning\WIN\pytest-book\venv\Scripts\python.exe
cachedir: .pytest_cache
rootdir: D:\SynologyDrive\CodeLearning\WIN\pytest-book
collecting ... collected 3 items
test_fixture_twofixtures-2.py::test_soso
test_fixture_twofixtures-2.py::test_cakan
test_fixture_twofixtures-2.py::test_cart
============================== 3 passed in 0.36s ==============================
Process finished with exit code 0
打开首页!
这是个登录模块!
PASSED [ 33%]case1:登录后执行搜索
PASSED [ 66%]case2:不登录就看
这是个登录模块!
PASSED [100%]case3:登录,加购物车
多个fixture的实例化顺序
由于存在fixture的并列关系、嵌套关系及不同作用域,所以需要仔细体会这些复杂关系及执行顺序,因此多个fixture的实例化顺序遵循以下原则:
- 高级别作用域的实例化(例如:session)先于低级别作用域的(例如:class或者function)实例化;
- 相同级别作用域的实例化,其实例化顺序遵循它们在测试用例中被声明的顺序,也就是形参的顺序,或者fixture之间的相互调用关系;
- 自动应用autouse的fixture,先于其同级别的其他fixture实例化。
代码如下:
import pytest
order = []
@pytest.fixture(scope="session", autouse=True)
def session1():
order.append("session1")
@pytest.fixture(scope="module", autouse=True)
def module1():
order.append("module1")
@pytest.fixture(autouse=True)
def function1(function3):
order.append("function1")
@pytest.fixture
def function3():
order.append("function3")
@pytest.fixture(autouse=True)
def autouse1():
order.append("autouse1")
@pytest.fixture(autouse=True)
def function2():
order.append("function2")
def test_order(module1):
assert order == ["session1", "module1", "autouse1", "function3", "function1", "function2"]
assert order == ["session1"]
执行结果如下:
session1拥有最高级的作用域(session),即使在测试用例test_order中最后被声明,它也是第1个被实例化的参数(参照第1条原则)。
module1拥有仅次于session级别的作用域(module),所以它是第2个被实例化的参数(参照第1条原则)。
function1、function2、function3、autouse1同属于function级别的作用域。
从test_order(function1,module1,function2,session1)形参的声明顺序中可以看出function1比function2先被实例化(参照第2条原则)。
function1的定义中又显式地调用了function3,所以function3比function1先被实例化(参照第2条原则)。
autouse1的定义中使能了autouse标记,所以它会在同级别的fixture之前被实例化,也就是在function3、function1、function2之前被实例化(参照第3条原则)。
所以在这个例子中fixture被实例化的顺序为session1、module1、autouse1、function3、function1、function2。
注意:除了autouse的fixture,需要测试用例显示声明(形参),不声明的参数不会被实例化。多个相同作用域的autouse fixture,其实例化顺序遵循fixture函数名的排序。
fixture返回工厂函数
常用使用场景:如果需要在一个测试用例中,多次使用同一个fixture实例,相对于直接返回数据,更好的方法是返回一个产生数据的工厂函数,并且,对于工厂函数产生的数据,也可以在fixture中对其管理。这里介绍的是一种思路,代码如下:
import pytest
@pytest.fixture
def make_customer_record():
created_records = []
def _make_customer_record(name):
record = models.Customer(name=name, orders=[])
created_records.append(record)
return record
yield _make_customer_record
for record in created_records:
record.destroy()
def test_customer_records(make_customer_record):
customer1 = make_customer_record("Lisa")
customer2 = make_customer_record("Mike")
customer3 = make_customer_record("Meredith")
高效地利用fixture实例
在测试期间,pytest只激活最少个数的fixture实例。如果拥有一个参数化的fixture,所有使用它的用例会在所创建的第一个fixture实例被销毁后,才会使用第二个实例。
下面这个例子使用了两个参数化的fixture,其中一个是模块级别的作用域,另一个是用例级别的作用域,并且使用print方法打印出它们的setup/teardown流程。
代码如下:
import pytest
@pytest.fixture(scope="module", params=["mod1", "mod2"])
def modarg(request):
param = request.param
print(" SETUP modarg", param)
yield param
print(" TEARDOWN modrag", param)
@pytest.fixture(scope="function", params=[1,2])
def otherarg(request):
param = request.param
print(" SETUP otherarg", param)
yield param
print(" TEARDOWN otherarg", param)
def test_0(otherarg):
print(" RUN test0 with otherarg", otherarg)
def test_1(modarg):
print(" RUN test1 with modarg", modarg)
def test_2(otherarg, modarg):
print(" RUN test2 with otherarg {} and modarg {}".format(otherarg, modarg))
执行结果如下:
PS D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-3> pytest -q -s test_minfixture.py
SETUP otherarg 1
RUN test0 with otherarg 1
. TEARDOWN otherarg 1
SETUP otherarg 2
RUN test0 with otherarg 2
. TEARDOWN otherarg 2
SETUP modarg mod1
RUN test1 with modarg mod1
. SETUP otherarg 1
RUN test2 with otherarg 1 and modarg mod1
. TEARDOWN otherarg 1
SETUP otherarg 2
RUN test2 with otherarg 2 and modarg mod1
. TEARDOWN otherarg 2
TEARDOWN modrag mod1
SETUP modarg mod2
RUN test1 with modarg mod2
. SETUP otherarg 1
RUN test2 with otherarg 1 and modarg mod2
. TEARDOWN otherarg 1
SETUP otherarg 2
RUN test2 with otherarg 2 and modarg mod2
. TEARDOWN otherarg 2
TEARDOWN modrag mod2
8 passed in 0.15s
PS D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-3>
mod1的TEARDOWN操作完成后,才开始mod2的SETUP操作。用例test_0独立完成测试,用例test_1和test_2都使用了模块级别的modarg,同时test_2也使用了用例级别的otherarg。它们执行的顺序是,test_1先使用mod1,接着test_2使用mod1和otherarg 1/otherarg 2,然后test_1使用mod2,最后test_2使用mod2和otherarg 1/otherarg 2,也就是说test_1和test_2共用相同的modarg实例,最少化地保留fixture的实例个数。