介绍
前面一篇讲了setup、teardown可以实现在执行用例前或结束后加入一些操作,但这种都是针对整个脚本全局生效的。
Fixture是pytest的非常核心功能之一,在不改变被装饰函数的前提下对函数进行功能增强,经常用于自定义测试用例前置和后置工作,更强大灵活。
一 、Fixture的优势
- fixture命名方式灵活,不局限于 setup 和teardown 那几个命名规则
- conftest.py 配置里可以实现数据共享,不需要 import就能够自动搜索需要的fixture
- fixture 配置不同的参数可以轻松实现跨文件共享前置、session会话共享
二、fixture工作原理
- 在普通函数上使用@Pytest.fixture()装饰器,声明函数为一个fixture函数
- 如果测试用例函数的参数列表中存在fixture的函数名或者使用@pytest.mark.usefixtures(fixture_name)
- 在测试执行阶段,pytest执行用例之前执行fixture函数功能
- 如果fixture装饰的函数无返回值,相当于用例前置操作,否则相当于传参
- 如果fixture设置了后置操作,则用例执行完成后进行执行
三、fixture的参数
@pytest.fixture(scope="function",params=None,autouse=True,ids=None,name=None)
def test():print("fixture初始化的参数")
参数
1、scope:可以理解成fixture的作用域。
(1) function:在函数之前和之后执行。函数测试用例时使用时需要引用参数 def test(seif,fixtures_def)
- 手动调用的方式是在测试用例的参数里面加入 fixture的名称。
- 如果说fixture有通过return或yield返回值的话,那么可以把这个值传递到测试用例中。值是通过固件的名字传递的。
参考用例:
import pytest@pytest.fixture(scope="function",autouse=False)
def test_fixture():print("fixture初始化的参数")return "success" #后面不能有其他语句#yield "success" #后面可以有其他语句#print("参数后置")class TestFixture:def test_one(self):print("测试用例1")def test_two(self,test_fixture):print("测试用例2")print(test_fixture)def test_three(self):print("测试用例1")if __name__ == '__main__':pytest.main()
执行结果:
(2) class:在类之前和之后执行。
- 手动调用的方式是在类的上面加@pytest.mark.usefixtures(“login”)装饰器
参考用例:
import pytest@pytest.fixture(scope="class",autouse=False)
def test_fixture():print("fixture初始化的参数")yield "success" #后面可以有其他语句print("参数后置")@pytest.mark.usefixtures("test_fixture")
class TestFixture:def test_one(self):print("测试用例1")def test_two(self,test_fixture):print("测试用例2")print(test_fixture)class TestStudy:def test_three(self):print("测试用例3")if __name__ == '__main__':pytest.main()
执行结果:
(3) module:每一个.py文件调用一次
参考用例:
import pytest@pytest.fixture(scope="module",autouse=False,)
def test_fixture():print("fixture初始化的参数")yield "success"print("参数后置")# 通过参数方式使用fixture
def test_01(test_fixture):print("类外面的测试用例")class TestFixture:def test_one(self):print("测试用例1")def test_two(self,test_fixture):print("测试用例2"+test_fixture)if __name__ == '__main__':pytest.main()
执行结果:
(4) package/session:在整个项目会话之前和之后,即开始执行pytest到结束测试一般用于打开浏览器、启动APP、登录等操作。会结合conftest.py文件来实现
2、autouse:默认False,需要用例手动调用该fixture;如果是True,所有作用域内的测试用例都会自动调用该fixture,无需传入fixture函数名,作用范围跟着scope走(注意使用)。
3、param:实现参数化
- 如何把值传到Fixture是通过fixture函数的参数里面加入request来接收参数。然后通过request.param来取值。(这里的param没有s)
@pytest.fixture(scope="function",autouse=False,params=read_yaml())
def test_fixture(request):print("fixture初始化的参数")yield (request.param)print("参数后置")
参考用例:
import pytest#读取数据文件
def read_yaml():return ['cehsi','houduan','qianduan']#return [{'a':'b'},{'c','d'}]@pytest.fixture(scope="function",autouse=False,params=read_yaml())
def test_fixture(request):print("fixture初始化的参数")yield request.paramprint("参数后置")class TestFixture:def test_one(self):print("测试用例1")def test_two(self,test_fixture):print("测试用例2"+test_fixture)#print("测试用例2"+str(test_fixture)) #数据是字典的话需要强转if __name__ == '__main__':pytest.main()
执行结果:
5、ids:不单独使用,需要与params配合使用,一对一关系,作用是对参数起别名。
参考用例:
@pytest.fixture(scope="function",autouse=False,params=read_yaml(),ids=['a','b','c'])
def test_fixture(request):print("fixture初始化的参数")yield request.paramprint("参数后置")
执行结果:
5、name:作用给Fixture取别名,用来区分不同的使用。
【特别注意】:一旦使用别名之后,那么fixture的名称就不能用了。只能用别名。
参考用例:
import pytest#读取数据文件
def read_yaml():return ['cehsi','houduan','qianduan']#return [{'a':'b'},{'c','d'}]@pytest.fixture(scope="function",autouse=False,params=read_yaml(),ids=['a','b','c'],name='abc')
def test_fixture(request):print("fixture初始化的参数")yield request.paramprint("参数后置")class TestFixture:def test_one(self):print("测试用例1")def test_two(self,abc):print("测试用例2"+abc)#print("测试用例2"+str(test_fixture)) #数据是字典的话需要强转if __name__ == '__main__':pytest.main()
注意
@pytest.mark.usefixtures():
- 在类声明上面加 @pytest.mark.usefixtures() ,代表这个类里面所有测试用例都会调用该fixture
- 可以叠加多个 @pytest.mark.usefixtures() ,先执行的放底层,后执行的放上层
- 可以传多个fixture参数,先执行的放前面,后执行的放后面
- 如果fixture有返回值,用 @pytest.mark.usefixtures() 是无法获取到返回值的,必须用传参的方式(方式一)
四、 Fixture中yield来实现teardown
用fixture实现teardown并不是一个独立的函数,而是用 yield 关键字来开启teardown操作,使用可参考上面的测试用例。
yield注意事项
- 如果yield前面的代码,即setup部分已经抛出异常了,则不会执行yield后面的teardown内容
- 如果测试用例抛出异常,yield后面的teardown内容还是会正常执行
yield+with的结合
# 官方例子
@pytest.fixture(scope="module")
def smtp_connection():with smtplib.SMTP("smtp.gmail.com", 587, timeout=5) as smtp_connection:yield smtp_connection # provide the fixture value
该 smtp_connection
连接将测试完成执行后已经关闭,因为 smtp_connection
对象自动关闭时, with 语句结束。
五、 addfinalizer 终结函数
@pytest.fixture(scope="module")
def test_addfinalizer(request):# 前置操作setupprint("==再次打开浏览器==")test = "test_addfinalizer"def fin():# 后置操作teardownprint("==再次关闭浏览器==")request.addfinalizer(fin)# 返回前置操作的变量return testdef test_anthor(test_addfinalizer):print("==最新用例==", test_addfinalizer)
注意事项
- 如果
request.addfinalizer()
前面的代码,即setup部分已经抛出异常了,则不会执行request.addfinalizer()
的teardown内容(和yield相似,应该是最近新版本改成一致了) - 可以声明多个终结函数并调用
六、Fixture结合conftest.py的使用
conftest.py的介绍
1、pytest会默认读取conftest.py里面的所有fixture
2、 conftest.py是专门用于存放fixture的配置文件。名称是固定的,不能变。
3、在 conftest.py文件里面所有的方法在调用时都不需要导包,pytest会自动查找。
5、 conftest.py文件可以有多个,并且多个 conftest.py文件里面的多个 fixture可以被一个用例调用
6、conftest.py只对同一个package下的所有测试用例生效
conftest.py文件一般写在项目的根路径下面,文件名不要写错
参考用例:
testcase.py
# 通过参数方式使用fixture
def test_01(abc):print("类外面的测试用例")class TestFixture:def test_one(self):print("测试用例1")def test_two(self,abc,sd):print("测试用例2"+abc+sd)if __name__ == '__main__':pytest.main()
conftest.py
import pytest@pytest.fixture(scope="function",autouse=False,name='abc')
def test_fixture():print("fixture初始化的参数")yield "success"print("参数后置")@pytest.fixture(scope="function",autouse=False,name='sd')
def test_study():print("多个fixture初始化的参数")yieldprint("多个参数后置")
执行结果:
七、Fixture的优先级
- 较高 scope 范围的fixture(session)在较低 scope 范围的fixture( function 、 class )【session > package > module > class > function】
- 具有相同作用域的fixture遵循测试函数中声明的顺序,并遵循fixture之间的依赖关系【在fixture_A里面依赖的fixture_B优先实例化,然后到fixture_A实例化】
八、setup、teardown、setup_class、teardown_class、fixture、conftest优先级
会话:fixture的session级别的优先级最高。
类:fixture的class级别的优先级最高。
类:setup_class
函数:fixture的function级别的优先级最高。
函数:setup
九、总结Pytest的执行过程
1、查询当前目录下的 conftest.py 文件;
2、查询当前目录下的 pytest.ini 文件,找到测试用例的位置;
3、查询用例目录下的 conftest.py 文件;
4、查询py文件中是否有setup_class、teardown_class;
5、再根据 pytest.ini 文件的测试用例规则去查询用例并执行。