pytest接口自动化测试框架 | 为什么要做pytest插件的二次开发
Posted COCOgsta
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了pytest接口自动化测试框架 | 为什么要做pytest插件的二次开发相关的知识,希望对你有一定的参考价值。
视频来源:B站《冒死上传!pytest接口自动化测试框架(基础理论到项目实战及二次开发)教学视频【软件测试】》
一边学习一边整理老师的课程内容及试验笔记,并与大家分享,侵权即删,谢谢支持!
- 插件优化案例展示之pytest-html
- 为什么哟啊做pytest插件的二次开发pytest是主流自动化框架,因此能够基于这个框架进行开发优化,推广更方便,企业使用成本更低pytest拥有海量的插件可以基于现有的pytest插件进行二次开发,并且插件一般有api文档,提供案例指导进行开发,学习起来非常方便 插件pytest-html API文档 https://pytest-html.readthedocs.io/en/latest/user_guide.html编写自己的Pytest插件
- 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
- 如何理解钩子函数(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.fixture()结合
pytest接口自动化测试结合单元测试框架pytest+数据驱动模型+allure
pytest接口自动化测试框架 | pytest结合二次封装实现接口自动化