• pytest接口自动化测试框架 | 为什么要做pytest插件的二次开发


    视频来源:B站《冒死上传!pytest接口自动化测试框架(基础理论到项目实战及二次开发)教学视频【软件测试】》

    一边学习一边整理老师的课程内容及试验笔记,并与大家分享,侵权即删,谢谢支持!


    1. 插件优化案例展示之pytest-html
    1. 为什么哟啊做pytest插件的二次开发pytest是主流自动化框架,因此能够基于这个框架进行开发优化,推广更方便,企业使用成本更低pytest拥有海量的插件可以基于现有的pytest插件进行二次开发,并且插件一般有api文档,提供案例指导进行开发,学习起来非常方便 插件pytest-html API文档 https://pytest-html.readthedocs.io/en/latest/user_guide.html编写自己的Pytest插件
    1. pytest执行测试原理

    【conftest、fixture】

    conftest也是pytest特有的本地测试配置文件,既可以用来设置项目级的fixture,也可以用来导入外部插件,还可以指定钩子函数。conftest.py文件名称是固定的,pytest会自动设别该文件,只作用在它的目录以及子目录。

    通过装饰器@pytest.fixture来告诉pytest某个特定的函数是一个fixture,然后用例可以直接把fixture当参数来调用

    【pytest可以通过Hook函数(pytest_runtest_makereport)获取用例执行的结果】

    源码:

    1. def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> TestReport:
    2. return TestReport.from_item_and_call(item, call)

    这里的item是测试用例,call是测试步骤,具体执行过程如下:

    *先执行when="setup"返回setup的执行结果

    *再执行when="call"返回call的执行结果

    *最后执行when="teardown"返回teardown的执行结果

    钩子函数=HOOK函数=海盗船长的钩子

    conftest.py

    1. import pytest
    2. """
    3. hook装饰器明天,今天先看他的运行过程
    4. """
    5. @pytest.hookimpl(hookwrapper=True, tryfirst=True)
    6. def pytest_runtest_makereport(item, call):
    7. print('-------------------------------')
    8. # 获取常规的钩子方法的调用结果,返回一个result对象
    9. out = yield
    10. print('用例的执行结果', out)
    11. # 获取调用结果的测试报告,返回一个report对象,report对象的属性
    12. # 包括when(setup, call, teardown三个值)、nodeid(测试用例的名字)、
    13. # outcome(用例执行的结果 passed, failed)
    14. report = out.get_result()
    15. print('测试报告: %s' % report)
    16. print('步骤:%s' % report.when)
    17. print('nodeid: %s' % report.nodeid)
    18. # 打印函数注释信息
    19. print('description: %s' % str(item.function.__doc__))
    20. print('运行结果: %s' % report.outcome)

    test_case01.py

    1. import pytest
    2. def test_01():
    3. """ 用例描述:展昭的用例"""
    4. print("用例运行时》》》》》")
    5. print("我是展昭")
    6. print("用例运行时》》》》》")
    7. if __name__ == '__main__':
    8. pytest.main(['-s'])

    运行结果:

    从运行结果可以看出,运行用例的过程会经历三个阶段:setup-call-teardown,每个阶段都会返回result对象和TestReport对象,以及对象属性

    通过钩子函数pytest_runtest_makereport可以捕捉到用例执行中的相关数据,这些数据就是我们用来对pytest-html测试报告插件二次开发的基础。

    【setup-call-teardown 3个环节失败的影响】

    fixture实现项目级的前置后置

    scope=session这个fixture这个项目只会启动一次,一般用来做项目级的环境的初始化和清理操作

    autouse=True代表自动运行,不需要用例主动去调用

    setup失败情况

    当setup执行失败了,setup的执行结果failed,后面的call用例不会再执行,teardown还是会执行

    此时,用例状态是error,也就是用例call都还没开始执行,就异常了

    call失败情况

    setup正常执行,但是测试用例call失败了,运行结果是failed

    teardown失败情况

    setup正常执行,测试用例call正常执行,teardown失败了

    运行结果:1 passed,1 error

    只取call的结果

    如果保证setup和teardown不报错情况,只关注测试用例本身的运行结果,可以加个判断:

    if report.when == "call"

    conftest.py

    1. import pytest
    2. """
    3. hook装饰器明天,今天先看他的运行过程
    4. """
    5. @pytest.hookimpl(hookwrapper=True, tryfirst=True)
    6. def pytest_runtest_makereport(item, call):
    7. print('-------------------------------')
    8. # 获取常规的钩子方法的调用结果,返回一个result对象
    9. out = yield
    10. print('用例的执行结果', out)
    11. # 获取调用结果的测试报告,返回一个report对象,report对象的属性
    12. # 包括when(setup, call, teardown三个值)、nodeid(测试用例的名字)、
    13. # outcome(用例执行的结果 passed, failed)
    14. report = out.get_result()
    15. # 只关注用例本身结果
    16. if report.when == "call":
    17. print('测试报告: %s' % report)
    18. print('步骤:%s' % report.when)
    19. print('nodeid: %s' % report.nodeid)
    20. # 打印函数注释信息
    21. print('description: %s' % str(item.function.__doc__))
    22. print('运行结果: %s' % report.outcome)
    23. @pytest.fixture(scope="session", autouse=True)
    24. def fix_a():
    25. print("setup 前置操作")
    26. # print("setup操作失败")
    27. yield
    28. print("teardown 后置操作 ")
    29. # assert 1==2

    1. 如何理解钩子函数(HOOK)

    钩子函数:

    1)是个函数,在系统消息触发时被系统调用

    2)不是用户自己触发的

    3)使用时直接编写函数体

    4)钩子函数的名称是确定的,当系统消息触发、自动会调用

    例如:pytest_runtest_makereport

    插件与Hook函数的关系:

    插件就是用1个或者多个Hook函数,也就是钩子函数构成的。如果要编写新的插件,或者是改进现有插件,都必须通过hook函数来进行。所以想掌握Pytest插件二次开发,必须搞定Hook函数。

    官网API地址:
    https://docs.pytest.org/en/latest/reference/reference.html?highlight=hooks#hooks

    5.python的yield的用法详解

    yield:return + generator的一部分

    PS:带yield的函数才是真正的生成器

    1. """
    2. 1. 程序开始执行以后,因为test函数中有yield关键字,
    3. 所以test函数并不会真的执行,而是先得到一个生成器g(相当于一个对象)
    4. 2. 直到调用next方法,test函数才正式开始执行,先执行test函数中的print方法,
    5. 然后进入while循环
    6. 3. 程序遇到yield关键字,然后把yield想成return,return一个8以后,程序停止,
    7. 并没有执行赋值给a的操作,此时next(g)语句执行完成,所以输出的前两行(第一个是while
    8. 上面的print函数,第二个是return出的结果)
    9. 4.程序执行print("*********************")
    10. 5.又开始执行下面的print(next(g)),这个时候和上面那个差不多,不过不同的是,
    11. 这个时候是从刚才那个next程序停止的地方开始执行的,也就是要执行的a的赋值操作,
    12. 这个时候要注意,这个时候赋值操作的右边是没有值的(因为刚才那个是return出去了,
    13. 并没有给赋值操作的左边传参数),所以这个时候,a赋值是None,所以接着下面输出的就是
    14. a:None
    15. 6.程序会继续在while里执行,又一次碰到yield,这个时候同样return 出8,然后程序停止,
    16. print函数输出的8就是这次return出的8
    17. """
    18. def tes():
    19. print("begin....")
    20. while True:
    21. a = yield 8
    22. print("a:", a)
    23. g = tes()
    24. print(next(g))
    25. print("**************************")
    26. print(next(g))

    运行结果:

    1. C:\Users\guoliang\AppData\Local\Programs\Python\Python36\python.exe D:/SynologyDrive/SourceCode/pytest/apitest/test_yield/test.py
    2. begin....
    3. 8
    4. **************************
    5. a: None
    6. 8
    7. Process finished with exit code 0

    总结:yield和return的关系和区别,带yield的函数是一个生成器,而不是一个函数了。这个生成器有一个函数就是next函数,next就相当于“下一步”生成哪个数, 这一次的next开始的地方是接着上一次next停止的地方执行的,所以调用next的时候是,生成器,并不会从test函数开始执行,只是接着上一步停止的地方开始,然后遇到yield后,return出要生成的数,此步就结束。

    生成器的send函数

    1. """
    2. 1. 程序开始执行以后,因为test函数中有yield关键字,
    3. 所以test函数并不会真的执行,而是先得到一个生成器g(相当于一个对象)
    4. 2. 直到调用next方法,test函数才正式开始执行,先执行test函数中的print方法,
    5. 然后进入while循环
    6. 3. 程序遇到yield关键字,然后把yield想成return,return一个8以后,程序停止,
    7. 并没有执行赋值给a的操作,此时next(g)语句执行完成,所以输出的前两行(第一个是while
    8. 上面的print函数,第二个是return出的结果)
    9. 4.程序执行print("*********************")
    10. 5.又开始执行下面的print(next(g)),这个时候和上面那个差不多,不过不同的是,
    11. 这个时候是从刚才那个next程序停止的地方开始执行的,也就是要执行的a的赋值操作,
    12. 这个时候要注意,这个时候赋值操作的右边是没有值的(因为刚才那个是return出去了,
    13. 并没有给赋值操作的左边传参数),所以这个时候,a赋值是None,所以接着下面输出的就是
    14. a:None
    15. 6.程序会继续在while里执行,又一次碰到yield,这个时候同样return 出8,然后程序停止,
    16. print函数输出的8就是这次return出的8
    17. """
    18. """
    19. 7.程序执行g.send(7),程序会从yield关键字那一行继续往下运行,
    20. send会把7这个值赋给变量a
    21. 8.由于send方法中包含next()方法,所以程序会继续向下运行执行print方法。
    22. 然后再次进入while循环
    23. 9.程序执行再次遇到yield关键字,yield会返回后面的值,程序再次暂停,直到再次调用next方法或者
    24. send方法
    25. """
    26. def tes():
    27. print("begin....")
    28. while True:
    29. a = yield 8
    30. print("a:", a)
    31. g = tes()
    32. print(next(g))
    33. print("**************************")
    34. print(g.send(7))

    运行结果:

    1. C:\Users\guoliang\AppData\Local\Programs\Python\Python36\python.exe D:/SynologyDrive/SourceCode/pytest/apitest/test_yield/test.py
    2. begin....
    3. 8
    4. **************************
    5. a: 7
    6. 8
    7. Process finished with exit code 0

    为什么用yield?

    例子:取0,1,2,3,....,1000

    节省内存,python3中range也改为了class(迭代器),不是一次性把所有数据都装到内存中

    conftest中的yield

  • 相关阅读:
    Ubuntu 23.10/24.04 LTS 放弃默认使用 snap 版 CUPS 打印堆栈
    西门子PLC的优势在哪呢?
    Linux三剑客之管道使用|nginx日志
    Patroni for opengauss 10:rewind
    计算机毕业设计springboot+vue基本微信小程序的外卖点餐订餐平台
    工业外观设计中色彩如何有效运用
    【Linux修炼】8.进程概念
    Spring-AOP
    零点到两点,我部署了一个es
    定时器与requestAnimationFrame、requestIdleCallback
  • 原文地址:https://blog.csdn.net/guolianggsta/article/details/125878829