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

Posted COCOgsta

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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)获取用例执行的结果】

源码:

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

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

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

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

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

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

conftest.py

import pytest

"""
    hook装饰器明天,今天先看他的运行过程
"""

@pytest.hookimpl(hookwrapper=True, tryfirst=True)
def pytest_runtest_makereport(item, call):
    print('-------------------------------')
    # 获取常规的钩子方法的调用结果,返回一个result对象
    out = yield

    print('用例的执行结果', out)

    # 获取调用结果的测试报告,返回一个report对象,report对象的属性
    # 包括when(setup, call, teardown三个值)、nodeid(测试用例的名字)、
    # outcome(用例执行的结果 passed, failed)
    report = out.get_result()

    print('测试报告: %s' % report)
    print('步骤:%s' % report.when)
    print('nodeid: %s' % report.nodeid)

    # 打印函数注释信息
    print('description: %s' % str(item.function.__doc__))
    print('运行结果: %s' % report.outcome)

test_case01.py

import pytest

def test_01():
    """ 用例描述:展昭的用例"""
    print("用例运行时》》》》》")
    print("我是展昭")
    print("用例运行时》》》》》")


if __name__ == '__main__':
    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

import pytest

"""
    hook装饰器明天,今天先看他的运行过程
"""

@pytest.hookimpl(hookwrapper=True, tryfirst=True)
def pytest_runtest_makereport(item, call):
    print('-------------------------------')
    # 获取常规的钩子方法的调用结果,返回一个result对象
    out = yield

    print('用例的执行结果', out)

    # 获取调用结果的测试报告,返回一个report对象,report对象的属性
    # 包括when(setup, call, teardown三个值)、nodeid(测试用例的名字)、
    # outcome(用例执行的结果 passed, failed)
    report = out.get_result()

    # 只关注用例本身结果
    if report.when == "call":
        print('测试报告: %s' % report)
        print('步骤:%s' % report.when)
        print('nodeid: %s' % report.nodeid)

        # 打印函数注释信息
        print('description: %s' % str(item.function.__doc__))
        print('运行结果: %s' % report.outcome)

@pytest.fixture(scope="session", autouse=True)
def fix_a():
    print("setup 前置操作")
    # print("setup操作失败")
    yield
    print("teardown 后置操作 ")
    # 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. 程序开始执行以后,因为test函数中有yield关键字,
    所以test函数并不会真的执行,而是先得到一个生成器g(相当于一个对象)
    2. 直到调用next方法,test函数才正式开始执行,先执行test函数中的print方法,
    然后进入while循环
    3. 程序遇到yield关键字,然后把yield想成return,return一个8以后,程序停止,
    并没有执行赋值给a的操作,此时next(g)语句执行完成,所以输出的前两行(第一个是while
    上面的print函数,第二个是return出的结果)
    4.程序执行print("*********************")
    5.又开始执行下面的print(next(g)),这个时候和上面那个差不多,不过不同的是,
    这个时候是从刚才那个next程序停止的地方开始执行的,也就是要执行的a的赋值操作,
    这个时候要注意,这个时候赋值操作的右边是没有值的(因为刚才那个是return出去了,
    并没有给赋值操作的左边传参数),所以这个时候,a赋值是None,所以接着下面输出的就是
    a:None
    6.程序会继续在while里执行,又一次碰到yield,这个时候同样return 出8,然后程序停止,
    print函数输出的8就是这次return出的8

"""

def tes():
    print("begin....")
    while True:
        a = yield 8
        print("a:", a)

g = tes()
print(next(g))
print("**************************")
print(next(g))

运行结果:

C:\\Users\\guoliang\\AppData\\Local\\Programs\\Python\\Python36\\python.exe D:/SynologyDrive/SourceCode/pytest/apitest/test_yield/test.py
begin....
8
**************************
a: None
8

Process finished with exit code 0

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

生成器的send函数

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

"""
"""
    7.程序执行g.send(7),程序会从yield关键字那一行继续往下运行,
    send会把7这个值赋给变量a
    8.由于send方法中包含next()方法,所以程序会继续向下运行执行print方法。
    然后再次进入while循环
    9.程序执行再次遇到yield关键字,yield会返回后面的值,程序再次暂停,直到再次调用next方法或者
    send方法
"""

def tes():
    print("begin....")
    while True:
        a = yield 8
        print("a:", a)

g = tes()
print(next(g))
print("**************************")
print(g.send(7))

运行结果:

C:\\Users\\guoliang\\AppData\\Local\\Programs\\Python\\Python36\\python.exe D:/SynologyDrive/SourceCode/pytest/apitest/test_yield/test.py
begin....
8
**************************
a: 7
8

Process finished with exit code 0

为什么用yield?

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

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

conftest中的yield

以上是关于pytest接口自动化测试框架 | 为什么要做pytest插件的二次开发的主要内容,如果未能解决你的问题,请参考以下文章

pytest接口自动化测试框架 | conftest.py

pytest接口自动化测试框架 | conftest.py和@pytest.fixture()结合

pytest接口自动化测试结合单元测试框架pytest+数据驱动模型+allure

pytest接口自动化测试框架 | pytest结合二次封装实现接口自动化

pytest接口自动化测试框架 | 接口自动化至yaml数据驱动

Python - pytest