领取资料,咨询答疑,请➕wei: June__Go
上一小节我们学习了pytest conftest.py文件的使用方法,本小节我们讲解一下fixture的yield关键字实现teardown后置操作。
当我们运行我们的测试时,我们会希望确保它们自己清理干净,这样它们就不会与任何其他测试混淆(同时我们也不会留下大量的测试数据来使系统膨胀)。pytest中的 Fixtures 提供了一个非常有用的拆卸系统,它允许我们定义每个 Fixture 自行清理所需的特定步骤。分别是通过yield或addfinalizer完成teardown清理,下面分别对这两种方式进行展开说明。
yield方式完成清理(推荐)
使用yield的fixture和普通的fixture基本差不多,但还是有以下两点:
- return改换成yield。
- 需要做清理的代码写在yield的下面。
使用yield后,fixture的执行顺序是这样的,如有两个fixture函数,fixture1和fixture2,先由pytest计算出fixture的线性顺序,它将运行每个fxiture直到它返回,然后移动到列表中的下一个夹具做同样的事情。等待测试完成后,pytest将返回到fxiture列表,但顺序相反,如果开始先执行了fixture1再执行了fixture2,那么后面就是先执行fixture2在执行fixture1.
示例:
test_demo.py
import pytest@pytest.fixture
def get_token():print("\n请求获取token\n")yield# 这里写你的清理代码print("\n注销token\n")def test_demo(get_token):print("测试用例")
运行结果:
============================= test session starts =============================
collecting ... collected 1 itemtest_demo.py::test_demo
请求获取tokenPASSED [100%]测试用例注销token============================== 1 passed in 0.02s ==============================
注:通过结果展示,我们可以清楚的看到我们的清理代码在测试用例运行过后运行了。这种方式比我们用setup和teardown要好很多,可以抽取公共代码,减少代码冗余。
多个yield的fixture执行顺序
test_demo.py
import pytest@pytest.fixture
def fn1():print("\n我是fn1,我在yield前面\n")yield 1print("\n我是fn1,我在yield后面\n")@pytest.fixture
def fn2(fn1):print("\n我是fn2,我在yield前面\n")yield 2print("\n我是fn2,我在yield后面\n")def test_demo(fn2):print("\n我是测试用例\n")
运行结果:
============================= test session starts =============================
collecting ... collected 1 itemtest_demo.py::test_demo
我是fn1,我在yield前面我是fn2,我在yield前面PASSED [100%]
我是测试用例我是fn2,我在yield后面我是fn1,我在yield后面============================== 1 passed in 0.02s ==============================
注:通过上述例子,我们可以很明显知道yield的fixture的执行顺序,pytest先计算出线性顺序,先执行了fn1,然后再执行了fn2,再执行test,清理时就先执行的fn2,然后再执行fn1,与开始的顺序相反。
yield遇到异常
1、如果其中一个用例在执行时出现异常,不影响yield后面的teardown执行,运行结果互不影响,并且全部用例执行完之后,yield唤起teardown操作。
test_demo.py
import pytest@pytest.fixture(scope="module")
def open():print("打开浏览器,并且打开百度首页")yieldprint("执行teardown!")print("最后关闭浏览器")def test_s1(open):print("用例1:搜索python-1")# 如果第一个用例异常了,不影响其他的用例执行raise NameError # 模拟异常def test_s2(open):print("用例2:搜索python-2")def test_s3(open):print("用例3:搜索python-3")if __name__ == "__main__":pytest.main(["-s", "test_f1.py"])
运行结果:
============================= test session starts =============================
collecting ... collected 3 itemstest_demo.py::test_s1 打开浏览器,并且打开百度首页
FAILED [ 33%]用例1:搜索python-1test_demo.py:11 (test_s1)
open = Nonedef test_s1(open):print("用例1:搜索python-1")# 如果第一个用例异常了,不影响其他的用例执行
> raise NameError # 模拟异常
E NameErrortest_demo.py:16: NameErrortest_demo.py::test_s2 PASSED [ 66%]用例2:搜索python-2test_demo.py::test_s3 PASSED [100%]用例3:搜索python-3
执行teardown!
最后关闭浏览器========================= 1 failed, 2 passed in 0.24s =========================
2、但是fixture函数如果在setup执行期间发生异常,那么pytest是不会去执行yield后面的teardown内容。
test_demo.py
import pytest@pytest.fixture(scope="module")
def open():10 / 0print("打开浏览器,并且打开百度首页")yieldprint("执行teardown!")print("最后关闭浏览器")def test_s1(open):print("用例1:搜索python-1")# 如果第一个用例异常了,不影响其他的用例执行raise NameError # 模拟异常def test_s2(open):print("用例2:搜索python-2")def test_s3(open):print("用例3:搜索python-3")if __name__ == "__main__":pytest.main(["-s", "test_f1.py"])
运行结果:
============================= test session starts =============================
collecting ... collected 3 itemstest_demo.py::test_s1 ERROR [ 33%]
test setup failed
@pytest.fixture(scope="module")def open():
> 10 / 0
E ZeroDivisionError: division by zerotest_demo.py:6: ZeroDivisionErrortest_demo.py::test_s2 ERROR [ 66%]
test setup failed
@pytest.fixture(scope="module")def open():
> 10 / 0
E ZeroDivisionError: division by zerotest_demo.py:6: ZeroDivisionErrortest_demo.py::test_s3 ERROR [100%]
test setup failed
@pytest.fixture(scope="module")def open():
> 10 / 0
E ZeroDivisionError: division by zerotest_demo.py:6: ZeroDivisionError============================== 3 errors in 0.27s ==============================
yield关键字+with上下文管理器的结合使用
yield 关键字 也可以配合 with 上下文管理器 语句使用。【使得代码更加精简】
示例
import pytest
import smtplib@pytest.fixture(scope="module")
def smtp_connection():with smtplib.SMTP("smtp.gmail.com", 587, timeout=5) as smtp_connection:yield smtp_connection
使用addfinalizer方法完成清理
除了yield可以实现teardown,我们也可以通过 request.addfinalizer() 的方式去注册终结函数来实现 teardown 用例的后置操作。addfinalizer 的用法跟 yield 是不同的, addfinalizer 需要你去注册一个或多个作为终结器使用的函数。
例如:增加一个函数 fin,并且注册成终结函数。
test_demo.py
import pytest@pytest.fixture(scope="module")
def test_addfinalizer(request):# 前置操作setupprint("\n===打开浏览器===\n")test = "test_addfinalizer"def fin():# 后置操作teardownprint("\n===关闭浏览器===\n")request.addfinalizer(fin)# 返回前置操作的变量return testdef test_case(test_addfinalizer):print("===最新用例===", test_addfinalizer)
运行结果:
============================= test session starts =============================
collecting ... collected 1 itemtest_demo.py::test_case
===打开浏览器===PASSED [100%]===最新用例=== test_addfinalizer===关闭浏览器================================= 1 passed in 0.02s ==============================
yield 与 addfinalizer 用法的区别
① addfinalizer 可以注册多个终结函数。当注册多个终结函数时,用例的后置操作同时会执行完所有的终结函数。
【注意】终结函数(用例后置操作函数)的执行顺序与其在fixture函数中注册的顺序相反(即先注册的终结函数后执行,后注册的终结函数先执行)
示例:
import pytest@pytest.fixture()
def demo_addfinalizer(request):print("====setup====")def fin1():print("====teardown1====")def fin2():print("====teardown2====")def fin3():print("====teardown3====")# 注册fin1、fin2、fin3为终结函数request.addfinalizer(fin1)request.addfinalizer(fin2)request.addfinalizer(fin3)def test_case1(demo_addfinalizer):print("====执行用例test_case1====")def test_case2(demo_addfinalizer):print("====执行用例test_case2====")def test_case3(demo_addfinalizer):print("====执行用例test_case3====")if __name__ == '__main__':pytest.main(__file__, '-s')
运行结果:
============================= test session starts =============================
collecting ... collected 3 itemstest_demo.py::test_case1 ====setup====
PASSED [ 33%]====执行用例test_case1====
====teardown3====
====teardown2====
====teardown1====test_demo.py::test_case2 ====setup====
PASSED [ 66%]====执行用例test_case2====
====teardown3====
====teardown2====
====teardown1====test_demo.py::test_case3 ====setup====
PASSED [100%]====执行用例test_case3====
====teardown3====
====teardown2====
====teardown1================================== 3 passed in 0.03s ==============================
②当执行测试用例时setup前置操作函数的代码执行错误或者发生异常时,addfinalizer 注册的终结函数依旧会执行。
③ yield 关键字可以返回setup前置操作函数中生成的测试数据,且 yield 关键字返回测试数据之后后续的代码依然可以运行。且后续执行的代码充当teardown后置操作函数。
④ addfinalizer 函数可以将一个或者多个函数注册为终结函数(一个或多个函数必须在fixture函数中定义),此时的终结函数为teardown后置操作函数;且最后可以使用 return 关键字返回setup前置操作函数生成的测试数据
最后感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走,希望可以帮助到大家!领取资料,咨询答疑,请➕wei: June__Go