一、fixture的用途
pytest fixture 与setup,teardown功能一样,但比之更加灵活,完全可以代替setup,teardown
1.做测试前后的初始化设置,如测试数据准备,链接数据库,打开浏览器等这些操作都可以使用fixture来实现
2.测试用例的前置条件可以使用fixture实现
3.支持经典的xunit fixture ,像unittest使用的setup和teardown
4.fixture可以实现unittest不能实现的功能,比如unittest中的测试用例和测试用例之间是无法传递参数和数据的,但是fixture却可以解决这个问题。
二、fixture的定义
fixture通过@pytest.fixture()装饰器装饰一个函数,那么这个函数就是一个fixture
import pytest
@pytest.fixture()
def login():
print('登陆方法')
yield
print('teardown')
def test_case1(login):
print('case1')
def test_case2():
print('case2')
def test_case3():
print('case3')
运行结果如下:
test_login.py::test_case2
test_login.py::test_case3
============================== 3 passed in 0.02s ===============================
Process finished with exit code 0
登陆方法
PASSED [ 33%]case1
teardown
PASSED [ 66%]case2
PASSED [100%]case3
三、共享 fixture 函数:conftest.py
1.conftest.py文件名字是固定的,不可以做任何修改
2.文件和用例文件在同一个目录下,那么conftest.py作用于整个目录
3.conftest.py文件不能被其他文件导入
4.所有同目录测试文件运行前都会执行conftest.py文件
在测试过程中,多个测试文件可能都要调用 fixture 函数,可以将其移动到 conftest.py 文件中。conftest.py 文件中的 fixture 函数不需要在测试函数中导入,可以被 pytest 自动识别,查找顺序从测试类开始,然后是测试模块,然后是 conftest.py 文件,最后是内置插件和第三方插件。
conftest.py :
import pytest
@pytest.fixture()
def login():
print("登录")
return 8
测试用例:
import pytest
class Test_Demo():
def test_case1(self):
print("\n开始执行测试用例1")
assert 1 + 1 == 2
def test_case2(self, login):
print("\n开始执行测试用例2")
print(login)
assert 2 + login == 10
def test_case3(self):
print("\n开始执行测试用例3")
assert 99 + 1 == 100
if __name__ == '__main__':
pytest.main()
结果如下:
PASSED [ 33%]
开始执行测试用例1
登录
PASSED [ 66%]
开始执行测试用例2
8
PASSED [100%]
开始执行测试用例3
四、yield方法
使用yield关键字可以实现setup/teardown的功能,在yield关键字之前的代码在case之前执行,yield之后的代码在case运行结束后执行。
import pytest
@pytest.fixture()
def login():
print("登录")
yield
print("退出登录")
class Test_Demo():
def test_case1(self):
print("\n开始执行测试用例1")
assert 1 + 1 == 2
def test_case2(self, login):
print("\n开始执行测试用例2")
assert 2 + 8 == 10
def test_case3(self):
print("\n开始执行测试用例3")
assert 99 + 1 == 100
if __name__ == '__main__':
pytest.main()
结果如下:
PASSED [ 33%]
开始执行测试用例1
登录
PASSED [ 66%]
开始执行测试用例2
退出登录
PASSED [100%]
开始执行测试用例3
五、addfinalizer方法
addfinalizer也可以实现环境的清理,实现与yield方法相同的效果,跟yield不同的是需要注册作为终结器使用的函数。
import pytest
@pytest.fixture()
def login(request):
print("登录")
def demo_finalizer():
print("退出登录")
request.addfinalizer(demo_finalizer)
class Test_Demo():
def test_case1(self):
print("\n开始执行测试用例1")
assert 1 + 1 == 2
def test_case2(self, login):
print("\n开始执行测试用例2")
assert 2 + 8 == 10
def test_case3(self):
print("\n开始执行测试用例3")
assert 99 + 1 == 100
if __name__ == '__main__':
pytest.main()
运行结果如下:
PASSED [ 33%]
开始执行测试用例1
登录
PASSED [ 66%]
开始执行测试用例2
退出登录
PASSED [100%]
开始执行测试用例3
六、fixture源码详解
fixture(scope=‘function’,params=None,autouse=False,ids=None,name=None):
scope:有四个级别参数"function"(默认),“class”,“module”,“session”。
params:一个可选的参数列表,它将导致多个参数调用fixture功能和所有测试使用它。
autouse:如果True,则为所有测试激活fixture func可以看到它。如果为False则显示需要参考来激活fixture。
ids:每个字符串id的列表,每个字符串对应于params这样他们就是测试ID的一部分。如果没有提供ID它们将从params自动生成。
name:fixture的名称。这默认为装饰函数的名称。如果fixture在定义它的统一模块中使用,夹具的功能名称将被请求夹具的功能arg遮蔽,解决这个问题的一种方法时将装饰函数命。
七、fixture的作用范围(scope)
(一)、conftest结合fixture的使用
fixture里面有个scope参数可以控制fixture的作用范围:session > module > class > function
- function 每一个函数或方法都会调用,默认为function
- class 每一个类调用一次,一个类可以有多个方法
- module(.py文件),每一个.py文件调用一次,该文件内又有多个function和class
- session(包) 是多个文件调用一次,可以跨.py文件调用,每个.py文件就是module
(二)、conftest应用场景
1、每个接口需共用到的token
2、每个接口需共用到的测试用例数据
3、每个接口需共用到的配置信息 1、scope='function’
import pytest
@pytest.fixture()
def login():
print('登陆方法')
yield ['username','passwd']
print('teardown')
def test_case1(login):
print(f'case1 login={login}')
def test_case2(login):
print('case2')
def test_case3(login):
print('case3')
运行结果如下:
============================= test session starts ==============================
platform darwin -- Python 3.6.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1 -- /usr/local/bin/python3.6
cachedir: .pytest_cache
rootdir: /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest, configfile: pytest.ini
collecting ... collected 3 items
test_login.py::test_case1
test_login.py::test_case2
test_login.py::test_case3
============================== 3 passed in 0.02s ===============================
Process finished with exit code 0
登陆方法
PASSED [ 33%]case1 login=['username', 'passwd']
teardown
登陆方法
PASSED [ 66%]case2
teardown
登陆方法
PASSED [100%]case3
teardown
2、scope=“class” 一个class里面多个用例都调用了此fixture,那么只在class里所有用例开始前执行一次
import pytest
@pytest.fixture(scope="class")
def login():
print("登录...")
结果:
登录...
PASSED [ 33%]
开始执行测试用例1
PASSED [ 66%]
开始执行测试用例2
PASSED [100%]
开始执行测试用例3
3、scope='module’
import pytest
@pytest.fixture(scope='module')
def login():
print('登陆方法')
yield ['username','passwd']
print('teardown')
def test_case1(login):
print(f'case1 login={login}')
def test_case2(login):
print('case2')
def test_case3(login):
print('case3')
运行结果如下:
============================= test session starts ==============================
platform darwin -- Python 3.6.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1 -- /usr/local/bin/python3.6
cachedir: .pytest_cache
rootdir: /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest, configfile: pytest.ini
collecting ... collected 3 items
test_login.py::test_case1
test_login.py::test_case2
test_login.py::test_case3
============================== 3 passed in 0.02s ===============================
Process finished with exit code 0
登陆方法
PASSED [ 33%]case1 login=['username', 'passwd']
PASSED [ 66%]case2
PASSED [100%]case3
teardown
4、scope='session’ 可以实现全局变量
多个.py文件只调用1次fixture
import pytest
@pytest.fixture(scope='session')
def get_token():
token = 'qeehfjejwjwjej11sss@22'
print('获取到token:%s' % token)
return token
import pytest
class Test(object):
def test2(self,get_token):
token = 'qeehfjejwjwjej11sss@22'
print("【执行test02.py-Test类-test2用例,获取get_token:%s】" %get_token)
assert get_token == token
if __name__=="__main__":
pytest.main(["-s","test02.py","test03.py"])
import pytest
class Test(object):
def test3(self,get_token):
token = 'qeehfjejwjwjej11sss@22'
print("【执行test03.py-Test类-test3用例,获取get_token:%s】" %get_token)
assert get_token == token
def test4(self,get_token):
token = 'qeehfjejwjwjej11sss@22'
print("【执行test03.py-Test类-test4用例,获取get_token:%s】" %get_token)
assert get_token == token
if __name__=="__main__":
pytest.main(["-s","test02.py","test03.py"])
"C:\Program Files\Python35\python.exe" C:/Users/wangli/PycharmProjects/Test/test/test02.py
============================= test session starts =============================
platform win32 -- Python 3.5.2, pytest-5.1.2, py-1.8.0, pluggy-0.12.0
rootdir: C:\Users\wangli\PycharmProjects\Test\test
collected 3 items
test02.py 获取到token:qeehfjejwjwjej11sss@22
【执行test02.py-Test类-test2用例,获取get_token:qeehfjejwjwjej11sss@22】
.
test03.py 【执行test03.py-Test类-test3用例,获取get_token:qeehfjejwjwjej11sss@22】
.【执行test03.py-Test类-test4用例,获取get_token:qeehfjejwjwjej11sss@22】
.
============================== 3 passed in 0.30s ==============================
Process finished with exit code 0
八、fixture自动调用(autouse=True)
autouse设置为True时,自动调用fixture功能。由于默认作用域为function,不指定scope则每个方法都会调用fixture方法。
import pytest
@pytest.fixture(autouse=True)
def login():
print('登陆方法')
yield ['username', 'passwd']
print('teardown')
def test_search1():
print('搜索用例1')
def test_search2():
print('搜索用例2')
运行结果如下:
============================= test session starts ==============================
test_search.py::test_search1 登陆方法
PASSED [ 50%]搜索用例1
teardown
test_search.py::test_search2 登陆方法
PASSED [100%]搜索用例2
teardown
============================== 2 passed in 0.01s ===============================
Process finished with exit code 0
九、fixture参数化(params)
@pytest.fixture有一个params参数,接受一个列表,列表中每个数据都可以作为用例的输入。也就说有多少数据,就会形成多少用例。可以通过’’‘request.param’’'来获取该次调用的参数。
import pytest
@pytest.fixture(params=['user1', 'user2', 'user3'])
def login(request):
print('登陆方法')
print('传入的参数为:'+request.param)
yield ['username', 'passwd']
print('teardown')
def test_case1(login):
print(f'case1 login={login}')
def test_case2(login):
print('case2')
def test_case3(login):
print('case3')
运行结果如下:
test_login.py::test_case1[user1]
test_login.py::test_case1[user2]
test_login.py::test_case1[user3]
test_login.py::test_case2[user1]
test_login.py::test_case2[user2]
test_login.py::test_case2[user3]
test_login.py::test_case3[user1] 登陆方法
传入的参数为:user1
PASSED [ 11%]case1 login=['username', 'passwd']
teardown
登陆方法
传入的参数为:user2
PASSED [ 22%]case1 login=['username', 'passwd']
teardown
登陆方法
传入的参数为:user3
PASSED [ 33%]case1 login=['username', 'passwd']
teardown
登陆方法
传入的参数为:user1
PASSED [ 44%]case2
teardown
登陆方法
传入的参数为:user2
PASSED [ 55%]case2
teardown
登陆方法
传入的参数为:user3
PASSED [ 66%]case2
teardown
登陆方法
传入的参数为:user1
PASSED [ 77%]case3
teardown
test_login.py::test_case3[user2] 登陆方法
传入的参数为:user2
PASSED [ 88%]case3
teardown
test_login.py::test_case3[user3] 登陆方法
传入的参数为:user3
PASSED [100%]case3
teardown
============================== 9 passed in 0.06s ===============================
十、参数化与fixture结合(indirect=True)
import pytest
@pytest.fixture(params=['user1', 'user2', 'user3'])
def login(request):
print('登陆方法')
print('传入的参数为:' + str(request.param))
yield ['username', 'passwd']
print('teardown')
@pytest.mark.parametrize('login', [
('username1', 'passwd1'),
('username2', 'passwd2')
], indirect=True)
def test_cart3(login):
print('购物车用例3')
运行结果如下:
test_cart.py::test_cart3[login0]
test_cart.py::test_cart3[login1]
============================== 2 passed in 0.02s ===============================
Process finished with exit code 0
登陆方法
传入的参数为:('username1', 'passwd1')
PASSED [ 50%]购物车用例3
teardown
登陆方法
传入的参数为:('username2', 'passwd2')
PASSED [100%]购物车用例3
teardown
|