视频来源:B站《冒死上传!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)是个函数,在系统消息触发时被系统调用
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