Pytest:fixture
前文提到,测试用例加上setup和teardown可以实现在用例之前或之后加入一些操作,但这种是整个脚本全局生效的。
如果想实现场景:用例1需要先登录,用例2不需要登录,用例3需要先登录。这种无法用setup和teardown来实现,需要自定义测试用例的预置条件。
Pytest:fixture
一、fixture优势
- 命名方式灵活,不局限于setup和teardown这几个命名
- conftest.py配置里可以实现数据共享,不需要import就能自动找到一些配置
- scope="module"可以实现多个.py 跨文件共享前置
- scope="session"可以实现多个.py 跨文件使用一个session来完成多个用例
二、使用装饰器标记fixture的功能
@pytest.fixture(scope=“function”,params=None,autouse=False,ids=None,name=None)
可以使用此装饰器(带或不带参数)来定义fixture功能。
fixture功能的名称可以在以后使用,引用时会在运行测试之前调用。
调用fixture的三种方式:
- 函数或类里面方法直接传fixture的函数参数名称
- test模块或类,使用装饰器@pytest.mark.usefixtures(fixturename)修饰;类里面所有测试用例都会调用该 fixture
- 测试功能可以直接使用fixture名称作为输入参数,在这种情况下,夹具实例从fixture返回功能将被注入
- 可以叠加多个 @pytest.mark.usefixtures() ,先执行的放底层,后执行的放上层
- 可以传多个 fixture 参数,先执行的放前面,后执行的放后面
- 如果 fixture 有返回值,用 @pytest.mark.usefixtures() 是无法获取到返回值的,必须用传参的方式(方式1)
- autouse=True:每个测试函数都会自动调用该fixture,无需传任何参数,作用范围跟着scope走(谨慎使用)
fixture参数
参数 | 说明 |
---|---|
scope | 用于控制fixture的作用范围,作用类似于Pytest的setup/teardown。 |
params | 一个可选的参数列表,它将导致多个参数调用fixture功能和所有测试使用它。 |
autouse | 如果为True,则为所有测试激活fixture func 可以看到它。 如果为False(默认值)则显式需要参考来激活fixture。 |
ids | 每个字符串id的列表,每个字符串对应于params 这样他们就是测试ID的一部分。 如果没有提供ID它们将从params自动生成。 |
name | fixture的名称,默认为装饰函数的名称。 如果fixture在定义它的同一模块中使用,夹具的功能名称将被请求夹具的功能arg遮蔽; 解决这个问题的一种方法是将装饰函数命名 fixture_<fixturename> 然后使用@ pytest.fixture(name ='<fixturename>') 。 |
fixture可以选择使用yield语句为测试函数提供它们的值,而不是return。 在这种情况下,yield语句之后的代码块作为拆卸代码执行,而不管测试结果如何。fixture功能必须只产生一次。
1. scope
- 有四个级别的参数,默认取值为function(函数级别),控制范围的排序为:session > module > class > function
取值 | 范围 | 说明 |
---|---|---|
function | 函数级 | 每一个函数或方法都会调用 |
class | 类级别 | 每个测试类只运行一次 |
module | 模块级 | 每一个.py文件调用一次 |
session | 会话级 | 每次会话只需要运行一次,会话内所有方法及类,模块都共享这个方法 |
scope = "function"
语法:
@pytest.fixture()
or
@pytest.fixture(scope=‘function’)
场景一:做为参数传入
import pytest
# fixture 函数(类中)作为多个参数传入
@pytest.fixture()
def login():
print("打开浏览器")
a = "account"
return a
@pytest.fixture()
def logout():
print("关闭浏览器")
class TestLogin:
# 传入login fixture
def test_001(self, login):
print("001传入了login fixture")
assert login == "account"
def test_002(self, logout):
print("002传入了logout fixture")
def test_003(self, login, logout):
print("003传入了两个fixture")
def test_004(self):
print("004未传入仍何fixture哦")
if __name__ =='__main__':
pytest.main()
从运行结果可以看出,fixture做为参数传入时,会在执行函数之前执行该fixture函数,再将值传入测试函数做为参数使用。这个场景多用于登录。
场景二:fixture传递测试数据
import pytest
@pytest.fixture()
def fixturefun():
return (1,2,3,4)
def test_one(fixturefun):
assert fixturefun[0] == 2
def test_two(fixturefun):
assert fixturefun[1] == 2
if __name__ == '__main__':
pytest.main(["-s", "test_fixt3.py"])
场景三:fixture的相互调用
import pytest
# fixture作为参数,互相调用传入
@pytest.fixture()
def account():
a = "account"
print("第一层fixture")
return a
# fixture的相互调用一定是要在测试类里调用这层fixture才会生效,普通函数单独调用是不生效的
@pytest.fixture()
def login(account):
print("第二层fixture")
class TestLogin:
def test_1(self, login):
print("直接使用第二层fixture,返回值为{}".format(login))
def test_2(self, account):
print("只调用account fixture,返回值为{}".format(account))
if __name__ == "__main__":
pytest.main(["-s", "test_fixture2.py"])
fixture相互调用时,会先找到所有的fixture,再一层层执行。
当有多层调用时,直接被调用的fixture(这里指login),不会将上一层的值自动返回,这里跟函数的调用一样。
注:
- 即使fixture之间支持相互调用,但普通函数直接使用fixture是不支持的,一定是在测试函数内调用才会逐级调用生效
- 有多层fixture调用时,最先执行的是最后一层fixture,而不是先执行传入测试函数的fixture
- 上层fixture的值不会自动return,类似函数相互调用的逻辑
scope = "class"
当测试类内的每一个测试方法都调用了fixture,fixture只在该class下所有测试用例执行前执行一次
测试类下面只有一些测试方法使用了fixture函数名,这样的话,fixture只在该class下第一个使用fixture函数的测试用例位置开始算,后面所有的测试用例执行前只执行一次。而该位置之前的测试用例就不管。
语法:
@pytest.fixture(scope=‘class’)
场景一:
import pytest
# fixture 作用域scope = 'class'
@pytest.fixture(scope='class')
def login():
print("scope为class")
class TestLogin:
def test_1(self, login):
print("用例1")
def test_2(self, login):
print("用例2")
def test_3(self, login):
print("用例3")
if __name__ == '__main__':
pytest.main(["-s", "test_fixture3.py"])
场景二:第一次调用的函数后面开始执行
import pytest
@pytest.fixture(scope='class')
def login():
a='123'
print("输入账号密码登陆")
class TestLogin:
def test_1(self):
print("用例1")
def test_2(self, login):
print("用例2")
def test_3(self, login):
print("用例3")
def test_4(self):
print("用例4")
if __name__ == '__main__':
pytest.main(["-s", "test_fixture3.py"])
scope = "module"
与class相同,只从.py文件开始引用fixture的位置生效
场景、只对开始引用的测试方法以下的类和方法生效
import pytest
# fixture scope = 'module'
@pytest.fixture(scope='module')
def login():
print("fixture的范围为module")
def test_01():
print("用例01")
def test_02(login):
print("用例02")
class TestLogin:
def test_1(self):
print("用例1")
def test_2(self):
print("用例2")
def test_3(self):
print("用例3")
if __name__ == '__main__':
pytest.main(["-s", "test_fixture3.py"])
scope = "session"
用法将在conftest.py文章内详细介绍
- session的作用范围是针对.py级别的,module是对当前.py生效,seesion是对多个.py文件生效
- session只作用于一个.py文件时,作用相当于module
- session多数与contest.py文件一起使用,作为全局fixture
2. params
- fixture的可选形参列表,支持列表传入
- 默认None,每个param的值,fixture都会去调用执行一次,类似for循环
- 可以与参数ids一起使用,作为每个参数的标识,详见ids
- 被fixture装饰的函数要调用是采用:Request.param
固定写法:
在pytest中有一个内建的fixture叫做request,代表fixture的调用状态,request有一个字段param。
可以使用类似@pytest.fixture(params=tasks_list)的方式,在fixture中使用request.param的方式作为返回值供测试函数调用。
其中tasks_list包含多少元素,该fixture就会被调用几次,分别作用在每个用到的测试函数上。
import pytest
@pytest.fixture(params = [1,2,{'a':1,'b':2},(4,5,5)])
def demo(request):
return request.param
def test_f(demo):
print("值{}".format(demo))
if __name__ == '__main__':
pytest.main(["-s", "test_fixture3.py"])
3. ids
- 用例标识ID
- 与params配合使用,一对一关系
场景:配置ids之后
import pytest
@pytest.fixture(params = [1,2,{'a':1,'b':2},(4,5,5)],ids=['one','two','three','four'])
def demo(request):
return request.param
def test_f(demo):
print("值{}".format(demo))
if __name__ == '__main__':
pytest.main(["-s", "test_fixture3.py"])
4.autouse
- 默认False
- 若为True,则每个测试函数都会自动调用该fixture,无需传入fixture函数名
场景:当autouse=True时,即使测试类和测试方法没有传入函数名,用例执行时也都调用了fixture
import pytest
@pytest.fixture(autouse="true")
def login():
print("当autouse为true时")
class TestLogin:
def test_1(self):
print("用例1")
def test_2(self):
print("用例2")
if __name__ == '__main__':
pytest.main(["-s", "test_fixture3.py"])
5.Name
-
fixture的重命名
-
使用 fixture 的测试函数会将 fixture 的函数名作为参数传递,但是 pytest
也允许将fixture重命名 -
如果使用了name,那只能将name传递,函数名不再生效
调用方法:@pytest.mark.usefixtures(‘fixture1’,‘fixture2’)
场景:重命名后,用函数名作为fixture名,执行用例时会找不到
import pytest
@pytest.fixture(name="new_fixture")
def test_name():
pass
# 使用name参数后,传入重命名函数,执行成功
def test_1(new_fixture):
print("使用name参数后,传入重命名函数,执行成功")
# 使用name参数后,传入函数名称,执行失败
def test_2(test_name):
print("使用name参数后,传入函数名称,执行失败")
if __name__ == '__main__':
pytest.main(["-s", "test_fixture3.py"])
三、conftest.py
fixture参数传入:scope=“function”
场景一、在同一个.py文件中,多个用例调用一个登陆功能:用例1需要先登录,用例2不需要登录,用例3需要先登录
import pytest
# 不带参数时默认 scope="function"
@pytest.fixture()
def login():
print("输入账号,密码先登录")
def test_a(login):
print("用例1:登陆之后,动作111")
# 不传login
def test_b():
print("用例2:不需要登陆,操作222")
def test_c(login):
print("用例3:登陆之后,动作333")
if __name__ == "__main__":
pytest.main(["-s", "test_fixt.py"])
conftest.py
conftest.py是什么?
conftest.py是fixture函数的一个集合,可以理解为公共的前置函数,提取出来放在一个文件里,然后供其它模块调用。
conftest.py使用场景
如果我们有很多个前置函数,写在各个py文件中是不很乱?再或者说,我们很多个py文件想要使用同一个前置函数的话,就不能把前置函数写在用例里,此时应该要有一个配置文件,单独管理一些预置的操作场景,pytest默认读取conftest.py里面的配置。
conftest.py使用原则
- conftest.py配置脚本名称是固定的,不能改名称
- conftest.py与运行的用例要在同一个pakage下,并且有__init__.py文件
- 不同于普通被调用的模块不需要import导入conftest.py,python会自动查找
场景二、使用conftest.py配置前置函数
# __init__.py
# conftest.py
import pytest
@pytest.fixture()
def login():
print("输入账号,密码先登录")
# test_fixt1.py
import pytest
def test_a(login):
print("用例1:登陆之后,动作111")
# 不传login
def test_b():
print("用例2:不需要登陆,操作222")
def test_c(login):
print("用例3:登陆之后,动作333")
if __name__ == '__main__':
pytest.main(["-s", "test_fixt1.py"])
# test_fixt2.py
import pytest
def test_d(login):
print("用例4:登陆之后,动作444")
# 不传login
def test_e():
print("用例2:不需要登陆,操作555")
if __name__ == '__main__':
pytest.main(["-s", "test_fixt2.py"])
上述场景中,单独运行test_fix1.py和test_fix2.py都能调用到login()方法,这样就能实现一些公共的操作可以单独拿出来放在conftest.py配置文件中。
conftest.py的作用域
conftest.py的作用范围跟随着scope的范围
py文件的目录结构
# 内层用例文件
import pytest
class TestLogin():
# 调用了内层fixture
def test_1(self,login2):
print("用例1")
# 调用了外层fixture
def test_2(self, login1):
print("用例2")
if __name__ == '__main__':
pytest.main(["-s", "test_b.py"])
# 内层conftest.py文件
import pytest
@pytest.fixture(scope="session")
def login2():
print("内层fixture前置")
# 外层用例文件
import pytest
class TestLogin():
# 调用了内层fixture
def test_1(self, login2):
print("用例1")
# 调用了外层fixture
def test_2(self, login1):
print("用例2")
if __name__ == '__main__':
pytest.main(["-s", "test_a.py"])
# 外层conftest.py文件
import pytest
@pytest.fixture()
def login1():
print("外层fixture")
内层用例执行时,内、外层的fixture文件都能被找到并执行。
外层用例执行时,外层调用内层的fixture文件执失败。