• 全网最牛自动化测试框架系列之pytest(7)-yield与终结函数


    【文章末尾给大家留下了大量的福利】

    通过上一篇文章,我们已经知道了pytest中,可以使用Fixture来完成运行测试用例之前的一些操作如连接数据库,以及测试执行之后自动去做一些善后工作如清空脏数据、关闭数据库连接等。

    我们已经学会了fixture函数的简单用法,但其实fixture还提供了两种非常优雅高效的写法,来完成测试执行前的处理操作与执行后的处理操作,即使用yieldaddfinalizer来实现。

    yield

    在fixture中的关键字yield主要有两个作用:

    • yield代替return进行参数的传递
    • 起到代码的分割作用,yield之前的代码为setup的作用,yield之后的代码为teardown的作用

    yield 与 return

    在 pytest 的fixture函数中可以使用yield代替return进行返回,示例如下:

    1. import pytest
    2. @pytest.fixture(autouse=True)
    3. def fixture_one():
    4. print("执行fixture_one")
    5. yield 1
    6. def test_e(fixture_one):
    7. print("执行test_e")
    8. print(fixture_one)
    9. assert fixture_one == 1
    10. if __name__ == '__main__':
    11. pytest.main(["-s"])

    运行结果如下:

    1. test_case_4.py::test_e
    2. 执行fixture_one
    3. PASSED [100%]执行test_e
    4. 1
    5. ============================== 1 passed in 0.12s ==============================

    从运行结果我们能看到fixture_one会返回1并传递给test_e,与return的作用完全一致。但如果仅仅只是这样使用的话,毫无意义,因为使用return足够了。所以,在实际的使用过程中我们一般会在yield后面加上teardown的代码。

    yield 与 teardown

    yield不进行参数传递

    对于不需要在前置操作中返回数据的 fixture 函数,加入yield,那么yield之前的代码为用例执行之前的操作(即setup),yield之后的代码为用例执行之后的操作(即teardown)。示例如下:

    1. import pytest
    2. @pytest.fixture()
    3. def fixture_demo():
    4. # setup
    5. print("\n连接数据库")
    6. yield
    7. # teardown
    8. print("清空脏数据")
    9. def test_case(fixture_demo):
    10. print("执行test_case")
    11. assert True
    12. if __name__ == '__main__':
    13. pytest.main(["-s"])

    运行结果如下:

    从结果中我们可以看出来,先执行了setup部分,再执行测试用例,最后执行teardown部分。

    yield进行参数传递

    yield可以将参数传递给测试用例。

    假设有这样一个场景,需要用到接口1的返回参数作为接口2的请求参数,即接口2依赖接口1,我们需要写一条测试用例对接口2进行测试,这个时候可以将接口1的请求写在前置中,如果是unittest框架则代码如下:

    1. import unittest
    2. import requests
    3. class TestDemo(unittest.TestCase):
    4. def setup(self):
    5. print("请求接口1")
    6. self.res_1 = requests.get(url=url_1, params=params_1)
    7. def test_api_2(self):
    8. print("验证接口2")
    9. # 将接口1的返回值self.res_1作为请求参数,请求接口2
    10. res = requests.post(url=url_2, data=self.res_1)
    11. # 断言
    12. self.assertEqual(res, "接口2预期的返回结果")
    13. def teardown(self):
    14. print("清空脏数据")

    pytest框架中使用fixture+yield则可编写如下:

    1. @pytest.fixture()
    2. def get_api_1_result():
    3. # setup
    4. res_1 = requests.get(url=url_1, params=params_1)
    5. yield res_1
    6. # teardown
    7. print("清空脏数据")
    8. def test_api_2(get_api_1_result):
    9. print("验证接口2")
    10. # 将接口1的返回值res_1作为请求参数,请求接口2
    11. res = requests.post(url=url_2, data=get_api_1_result)
    12. # 断言
    13. assert res == "接口2预期的返回结果"

    其中,fixture 会先通过yield返回res_1,并传入测试用例test_api_2中,test_api_2运行完成后再去执行yield后面的代码,即执行print("清空脏数据")

    通过以上对比unittestsetupteardown以及参数的传递,我们就能很直观的看出pytestyield的使用方式,此处代码仅为示例。

    yield 的执行顺序

    有时候我们会遇到一个fixture函数调用另一个或多个fixture函数,且这些函数中可能含有yield,我们先看示例,代码如下:

    1. import pytest
    2. @pytest.fixture
    3. def fixture_1():
    4. print("\n执行fixture_1")
    5. yield 1
    6. print("\n执行fixture_1的teardown代码")
    7. @pytest.fixture
    8. def fixture_2(fixture_1):
    9. print("\n执行fixture_2")
    10. yield 2
    11. print("\n执行fixture_2的teardown代码")
    12. @pytest.fixture
    13. def fixture_add(fixture_1, fixture_2):
    14. print("\n执行fixture_add")
    15. result = fixture_1 + fixture_2
    16. yield result
    17. print("\n执行fixture_add的teardown代码")
    18. def test_demo(fixture_add):
    19. print("\n执行测试函数test_demo")
    20. assert fixture_add == 3
    21. if __name__ == '__main__':
    22. pytest.main(["-s"])

    运行结果如下:

    1. rootdir: E:\blog\python接口自动化\apiAutoTest, configfile: pytest.ini
    2. plugins: html-2.1.1, metadata-1.10.0, ordering-0.6, rerunfailures-9.1.1
    3. collecting ... collected 1 item
    4. test_case_4.py::test_demo
    5. 执行fixture_1
    6. 执行fixture_2
    7. 执行fixture_add
    8. PASSED [100%]
    9. 执行测试函数test_demo
    10. 执行fixture_add的teardown代码
    11. 执行fixture_2的teardown代码
    12. 执行fixture_1的teardown代码
    13. ============================== 1 passed in 0.12s ==============================

    从结果可以看出:

    test_demo 测试函数执行之前:先执行了 fixture_1,再执行fixture_2,最后执行fixture_add,注意此时都是执行yield之前的的代码;

    test_demo 测试函数执行之后:先执行了 fixture_add,再执行fixture_2,最后执行fixture_1,注意此时都是执行yield之后的的代码。

    因此,当一个fixture函数调用另一个或多个fixture函数,且fixture函数中含有yield时,被测试函数调用时有如下执行顺序:

    • 测试函数执行之前,pytest会根据fixture函数之间的线性关系顺序调用,即依次执行yield之前的代码

    • 而测试函数执行结束后,pytest会根据之前的顺序反方向执行fixture函数中yield之后的代码

    finalizer

    finalizer即终结器 (终结函数),与unittest中的teardown作用一样,测试用例执行完成后再执行终结器代码。

    在pytest中,fixture除了使用 yield 进行 teardown 之外,还可以使用request.addfinalizer()定义finalizer来进行后置操作。

    使用addfinalizer,需要在定义 fixture 函数时传入request,并以内嵌函数的形式进行定义。终结函数可以定义一个或多个。

    定义单个终结函数

    示例如下:

    1. import pytest
    2. @pytest.fixture
    3. def fixture_demo(request):
    4. print("\nsetup:每个case开始前执行一次")
    5. # 定义终结函数
    6. def finalizer_demo():
    7. print("\nteardown:每个case完成后执行一次")
    8. # 将finalizer_demo注册为终结函数
    9. request.addfinalizer(finalizer_demo)
    10. def test_2(fixture_demo):
    11. print("\n执行test_2")
    12. def test_1(fixture_demo):
    13. print("\n执行test_1")
    14. if __name__ == '__main__':
    15. pytest.main()

    运行结果如下:

    1. rootdir: E:\blog\python接口自动化\apiAutoTest, configfile: pytest.ini
    2. plugins: html-2.1.1, metadata-1.10.0, ordering-0.6, rerunfailures-9.1.1
    3. collecting ... collected 2 items
    4. test_module_02\test_case_3.py::test_2
    5. setup:每个case开始前执行一次
    6. PASSED [ 50%]
    7. 执行test_2
    8. teardown:每个case完成后执行一次
    9. test_module_02\test_case_3.py::test_1
    10. setup:每个case开始前执行一次
    11. PASSED [100%]
    12. 执行test_1
    13. teardown:每个case完成后执行一次
    14. ============================== 2 passed in 0.03s ==============================

    从结果可以看出来,在测试用例执行完后会执行addfinalizer函数,效果与执行yield后的代码一致。

    定义多个终结函数

    示例如下:

    1. import pytest
    2. @pytest.fixture
    3. def fixture_demo(request):
    4. print("\nsetup:每个case开始前执行一次")
    5. # 定义终结函数
    6. def finalizer_demo_1():
    7. print("\nteardown1:每个case完成后执行一次")
    8. def finalizer_demo_2():
    9. print("\nteardown2:每个case完成后执行一次")
    10. # 注册为终结函数
    11. request.addfinalizer(finalizer_demo_1)
    12. request.addfinalizer(finalizer_demo_2)
    13. def test_2(fixture_demo):
    14. print("\n执行test_2")
    15. def test_1(fixture_demo):
    16. print("\n执行test_1")
    17. if __name__ == '__main__':
    18. pytest.main()

    PYTHON 复制 全屏

    运行结果如下:

    1. rootdir: E:\blog\python接口自动化\apiAutoTest, configfile: pytest.ini
    2. plugins: html-2.1.1, metadata-1.10.0, ordering-0.6, rerunfailures-9.1.1
    3. collecting ... collected 2 items
    4. test_module_02\test_case_3.py::test_2
    5. setup:每个case开始前执行一次
    6. PASSED [ 50%]
    7. 执行test_2
    8. teardown2:每个case完成后执行一次
    9. teardown1:每个case完成后执行一次
    10. test_module_02\test_case_3.py::test_1
    11. setup:每个case开始前执行一次
    12. PASSED [100%]
    13. 执行test_1
    14. teardown2:每个case完成后执行一次
    15. teardown1:每个case完成后执行一次
    16. ============================== 2 passed in 0.02s ==============================

    CPP 复制 全屏

    从结果可以看出,上面示例中测试函数执行完成后,先执行了finalizer_demo_2,后执行finalizer_demo_1

    所以, 当有多个终结函数被执行时,执行顺序与注册顺序是相反的

    总结

    实际项目中,可以视情况进行选择,但一般情况下,推荐使用yield,因为这样代码更加简洁高效,且阅读性更强更容易维护。

     

      重点:学习资料学习当然离不开资料,这里当然也给你们准备了600G的学习资料

    【需要的可以扫描文章末尾的qq群二维码自助拿走】

    【记得(备注“csdn000”)】

    【或私信000】

    群里的免费资料都是笔者十多年测试生涯的精华。还有同行大神一起交流技术哦。

    项目实战:

    大型电商平台:

    全套软件测试自动化测试教学视频

    300G教程资料下载【视频教程+PPT+项目源码】

    全套软件测试自动化测试大厂面经

    python自动化测试++全套模板+性能测试

    听说关注我并三连的铁汁都已经升职加薪暴富了哦!!!!

  • 相关阅读:
    微信小程序:(更新)云开发微群人脉
    【Matplotlib绘制图像大全】(三):水平柱状图
    学习c语言中的几道习题(小有难度)!
    多行文本转成一行的实现方法
    得知女儿被猥亵,35岁男子将对方打至轻伤二级,法院作出不起诉决定
    一个案例体会Vue的优势
    3474. 坠落的蚂蚁
    为K8S集群准备Ceph存储
    python基础--函数的应用、lambda表达式以及高阶函数
    想学硬件,该学什么啊?
  • 原文地址:https://blog.csdn.net/m0_60054525/article/details/126611028