pytest.fixture
- setup和teardown函数能够在测试用例之前或者之后添加一些操作,但这种是整个脚本全局生效的。
- 如果我们想实现以下场景:用例1需要登录,用例2不需要登录,用例3需要登录,这就无法直接用setup和teardown来同一个类中实现,却可以通过pytest.fixture实现。
fixture基本使用
fixture是pytest特有的功能,它以装饰器形式定义在函数上面, 在编写测试函数的时候,可以将被fixture装饰的函数的名字做为测试函数的参数,运行测试脚本时,执行测试函数时就会自动传入被fixture装饰的函数的返回值。
import pytest
import requests
# [email protected]装饰函数
@pytest.fixture()
def get_web_url():
print('get_web_url')
return 'https://www.baidu.com'
# 1. 把上面函数名作为测试用例的参数
def test_web(get_web_url):
# 2. 测试用例调用前,需要先确定形参get_web_url,就是调用get_web_url
print('test_web')
print(get_web_url) # 测试用例内部使用get_web_url,就是使用它返回值
r = requests.get(get_web_url)
assert r.status_code == 200, '测试成功'
运行结果如下:
plugins: tavern-1.16.3, openfiles-0.3.2, arraydiff-0.3, allure-pytest-2.9.45, doctestplus-0.3.0, remotedata-0.3.1
collected 1 item
pytest_code5.py get_web_url
test_web
https://www.baidu.com
.
==================================================================== 1 passed in 0.19s ====================================================================
conftest.py文件
共享fixture函数
如果在测试中多个测试文件中用例用到同一个的fixture函数,则可以将其移动到conftest.py文件中,所需的fixture对象会自动被pytest发现,而不需要再每次导入
conftest.py文件名固定
在conftest.py文件中实现共用的fixture函数
conftest.py内容如下:
# pytest_fixture/conftest.py 文件名不能改变,否则无效
import pytest
# 默认是function级别的
@pytest.fixture()
def login_fixture():
"""可以把函数作为参数传递"""
print("\n公用的登陆方法")
test_fixture1.py内容如下:
# pytest_fixture/test_fixture1.py
import pytest
def test_get_carts():
"""购物车不需要登陆"""
print("\n测试查询购物车,无需登录")
class TestFixtures(object):
"""需要登陆的信息"""
def test_get_user_info(self, login_fixture):
print("获取用户信息")
def test_order_info(self, login_fixture):
print("查询订单信息")
def test_logout(login_fixture):
"""登出"""
print("退出登录")
if __name__ == '__main__':
pytest.main(['-s', 'test_fixture1.py'])
运行结果如下:
test_fixture1.py .
测试查询购物车,无需登录
公用的登陆方法
.获取用户信息
公用的登陆方法
.查询订单信息
公用的登陆方法
.退出登录
[100%]
============================== 4 passed in 0.03s ===============================
pytest.mark.usefixtures
- 可以使用@pytest.mark.usefixtures('fixture函数名字符串')来装饰测试类和测试方法
test_fixture2.py内容如下:
# pytest_fixture/test_fixture2.py
import pytest
def test_get_carts():
"""购物车不需要登陆"""
print("\n测试查询购物车,无需登录")
@pytest.mark.usefixtures('login_fixture')
class TestFixtures(object):
"""需要登陆的信息"""
def test_get_user_info(self):
print("获取用户信息")
def test_order_info(self):
print("查询订单信息")
@pytest.mark.usefixtures('login_fixture')
def test_logout():
"""登出"""
print("退出登录")
if __name__ == '__main__':
pytest.main(['-s', 'test_fixture2.py'])
右键运行如下:
test_fixture2.py .
测试查询购物车,无需登录
公用的登陆方法
.获取用户信息
公用的登陆方法
.查询订单信息
公用的登陆方法
.退出登录
[100%]
============================== 4 passed in 0.03s ===============================
pytest.fixture参数
pytest.fixture(scope='function', params=None, autouse=False, ids=None, name=None)
- scope: 被标记方法的作用域, 可以传入以下四个值;
-- "function": 默认值,每个测试用例都要执行一次 fixture 函数
-- "class": 作用于整个类, 表示每个类只运行一次 fixture 函数
-- "module": 作用于整个模块, 每个 module 的只执行一次 fixture 函数
-- "session": 作用于整个 session , 一次 session 只运行一次 fixture - params: list 类型,默认 None, 接收参数值,对于 param 里面的每个值,fixture 都会去遍历执行一次。
- autouse: 是否自动运行,默认为 false, 为 true 时此 session 中的所有测试函数都会调用 fixture
scope参数
- function:设置为function,表示每个测试方法都要执行一次
# function:设置为function,表示每个测试方法都要执行一次
import pytest
@pytest.fixture(scope='function')
# @pytest.fixture() # 和上面等价
def foo():
print('foo')
def test_1(foo):
print('普通测试用例111111')
def test_2():
print('普通测试用例22222')
class TestClass(object):
def test_one(self, foo):
print('类实例方法测试用例111111')
def test_two(self, foo):
print('类实例方法测试用例22222')
运行如下:
test_9_scope_function.py foo
普通测试用例111111
.普通测试用例22222
.foo
类实例方法测试用例111111
.foo
类实例方法测试用例22222
.
==================================================================== 4 passed in 0.03s ====================================================================
- class:设置为 class 时代表这个类中只会执行一次
import pytest
@pytest.fixture(scope='class')
def foo():
print('foo')
def test_1(foo):
print('普通测试用例111111')
def test_2(foo):
print('普通测试用例22222')
class TestClass(object):
def test_one(self, foo):
print('类实例方法测试用例111111')
def test_two(self, foo):
print('类实例方法测试用例22222')
运行如下:
test_10_scope_class.py foo
普通测试用例111111
.foo
普通测试用例22222
.foo
类实例方法测试用例111111
.类实例方法测试用例22222
.
==================================================================== 4 passed in 0.03s ====================================================================
- module:设置为 module 时代表这个模块中只会执行一次
- session:整个 session 都只会执行一次
# module:只会在最开始的时候传入参数执行1次
# session:只会在session开始传入参数的时候执行1次
import pytest
@pytest.fixture(scope='module')
# @pytest.fixture(scope='session')
def foo():
print('foo')
def test_1(foo):
print('普通测试用例111111')
def test_2(foo):
print('普通测试用例22222')
class TestClass(object):
def test_one(self, foo):
print('类实例方法测试用例111111')
def test_two(self, foo):
print('类实例方法测试用例22222')
运行如下:
test_11_scope_module.py foo
普通测试用例111111
.普通测试用例22222
.类实例方法测试用例111111
.类实例方法测试用例22222
.
==================================================================== 4 passed in 0.03s ====================================================================
params参数
- pytest.fixture(params=None) 的params参数接收list类型的参数
- 对于param里面的每个值,fixture函数都会去遍历执行一次
- 相应的每次都会驱动使用fixture函数的测试函数执行一次。
import pytest
def check_password(password):
"""
检查密码是否合法
:param password: 长度是 8 到 16
:return:
"""
pwd_len = len(password)
if pwd_len < 8:
return False
elif pwd_len > 16:
return False
else:
return True
@pytest.fixture(params=['1234567', '12345678', '123456789', '123456789012345', '1234567890123456', '12345678901234567'])
def password(request):
return request.param
def test_check_password(password):
print(password)
print(check_password(password))
运行如下:
test_13_params.py 1234567
False
.12345678
True
.123456789
True
.123456789012345
True
.1234567890123456
True
.12345678901234567
False
.
==================================================================== 6 passed in 0.03s ====================================================================
import pytest
@pytest.fixture(params=['admin', 'zhangsan', 'lisi'])
def username(request):
return request.param
@pytest.fixture(params=['1234567', '12345678', '123456789', '123456789012345', '1234567890123456', '12345678901234567'])
def password(request):
return request.param
def test_check_regist(username, password):
print(username, '=====', password)
if __name__ == '__main__':
pytest.main(['-s', 'test_14_params2.py'])
运行如下:
test_14_params2.py [100%]
============================== 18 passed in 0.06s ==============================
Process finished with exit code 0
.admin ===== 1234567
.admin ===== 12345678
.admin ===== 123456789
.admin ===== 123456789012345
.admin ===== 1234567890123456
.admin ===== 12345678901234567
.zhangsan ===== 1234567
.zhangsan ===== 12345678
.zhangsan ===== 123456789
.zhangsan ===== 123456789012345
.zhangsan ===== 1234567890123456
.zhangsan ===== 12345678901234567
.lisi ===== 1234567
.lisi ===== 12345678
.lisi ===== 123456789
.lisi ===== 123456789012345
.lisi ===== 1234567890123456
.lisi ===== 12345678901234567
autouse参数
pytest.fixture(autouse=False) 的autouse参数默认为False, 不会自动执行;设置为True时,当前运行的所有测试函数在运行前都会执行fixture函数
import pytest
@pytest.fixture(autouse=True)
def before():
print('\nbefore each test')
class Test2:
def test_1(self):
print('test_5')
def test_2(self):
print('test_6')
运行如下:
test_15_autouse.py
before each test
test_5
.
before each test
test_6
.
==================================================================== 2 passed in 0.03s ====================================================================
pytest.mark标记
pytest.mark下提供了标记装饰器,除了之前我们使用的pytest.mark.usefixtures()装饰器以外,还有一些常用的标记装饰器
装饰器 | 作用 |
---|---|
pytest.mark.xfail() | 将测试函数标记为预期失败。 |
pytest.mark.skip() | 无条件地跳过测试函数 |
pytest.mark.skipif() | 有条件地跳过测试函数 |
pytest.mark.parametrize() | 参数化Fixture方法和测试函数。 |
pytest.mark.usefixtures() | 使用类、模块或项目中的Fixture方法。 |
标志预期失效
- 要测试的功能或者函数还没有实现,这个时候执行测试一定是失败的。我们通过 xfail 来标记某个测试方法一定会失败
- xfail(condition=True, reason=None, raises=None, run=True, strict=False)
-- condition:标记预期失败的条件,如果条件为 False,那么这个标记无意义
-- reason:标记预期失败的原因说明
import pytest
class Test_ABC:
def setup_class(self):
print("\nsetup")
def teardown_class(self):
print("\nteardown")
def test_a(self):
print("\ntest_a")
@pytest.mark.xfail(condition=False, reason="预期失败")
def test_b(self):
print("\ntest_b")
assert 0
@pytest.mark.xfail(condition=True, reason="预期失败")
def test_c(self):
print("\ntest_c")
assert 0
if __name__ == '__main__':
pytest.main(['-s', 'test_22.py'])
运行如下:
test_22.py
setup
.
test_a
F
test_b
test_22.py:12 (Test_ABC.test_b)
self =
@pytest.mark.xfail(condition=False, reason="预期失败")
def test_b(self):
print("\ntest_b")
> assert 0
E assert 0
test_22.py:16: AssertionError
x
test_c
self =
@pytest.mark.xfail(condition=True, reason="预期失败")
def test_c(self):
print("\ntest_c")
> assert 0
E assert 0
test_22.py:21: AssertionError
teardown
Assertion failed
[100%]
=================================== FAILURES ===================================
_______________________________ Test_ABC.test_b ________________________________
self =
@pytest.mark.xfail(condition=False, reason="预期失败")
def test_b(self):
print("\ntest_b")
> assert 0
E assert 0
test_22.py:16: AssertionError
----------------------------- Captured stdout call -----------------------------
test_b
=========================== short test summary info ============================
FAILED test_22.py::Test_ABC::test_b - assert 0
==================== 1 failed, 1 passed, 1 xfailed in 0.08s ====================
跳过测试函数
无条件跳过
使用场景: 根据特定条件、不执行标识的测试函数
- skip(reason=None)
---reason: 标注原因
import pytest
import pytest
class Test_ABC:
def setup_class(self):
print("\nsetup")
def teardown_class(self):
print("\nteardown")
def test_a(self):
print("test_a")
# 开启跳过标记
@pytest.mark.skip(reason="无条件跳过不执行,就是任性顽皮")
def test_b(self):
print("test_b")
if __name__ == '__main__':
pytest.main(['-s', 'test_23.py'])
运行效果如下:
test_23.py
setup
.test_a
s
Skipped: 无条件跳过不执行,就是任性顽皮
teardown
[100%]
========================= 1 passed, 1 skipped in 0.03s =========================
有条件跳过
使用场景: 根据特定条件、不执行标识的测试函数
- skipif(condition, reason=None)
--- condition: 跳过的条件,必传参数
--- reason: 标注原因
import pytest
class Test_ABC:
def setup_class(self):
print("\nsetup")
def teardown_class(self):
print("\nteardown")
def test_a(self):
print("test_a")
# 开启跳过标记
@pytest.mark.skipif(condition=1, reason="有条件跳过不执行,依旧任性顽皮")
# @pytest.mark.skipif(condition=0, reason="条件跳不成立,无法跳过")
def test_b(self):
print("test_b")
if __name__ == '__main__':
pytest.main(['-s', 'test_24.py'])
运行效果如下:
test_24.py
setup
.test_a
s
Skipped: 有条件跳过不执行,依旧任性顽皮
teardown
[100%]
========================= 1 passed, 1 skipped in 0.03s =========================
参数化
使用场景:需要测试一组不同的数据,而测试过程是一样的,这种情况下我们可以写一个测试方法,并且测试方法通过参数接受数据。通过遍历数据并且调用测试方法来完成测试。
作用: 参数化fixture方法和测试函数, 方便测试函数对测试属性的获取。
parametrize(argnames, argvalues, indirect=False, ids=None, scope=None)
--- argnames:参数名, 以逗号分隔的字符串,表示一个或多个参数名称,或参数字符串的列表/元组. 参数名为几个,就会运行几次。
--- argvalues:
----参数对应值,类型必须为 list
----当参数为一个时,参数格式:[value1,value2,...]
----当参数个数大于一个时,格式为: [(param_value1,param_value2),...]
import pytest
class Test_ABC:
def setup_class(self):
print("setup")
def teardown_class(self):
print("teardown")
def test_a(self):
print("test_a")
@pytest.mark.parametrize("a", [3, 6])
def test_b(self, a):
print(f"test_b data:a={a}")
@pytest.mark.parametrize(["a","b"],[(1,2),(3,4)])
def test_c(self, a, b):
print(f"test_c a: {a}; b: {b}")
if __name__ == '__main__':
pytest.main(['-s', 'test_25.py'])
运行效果如下
test_25.py [100%]
============================== 5 passed in 0.04s ===============================
Process finished with exit code 0
setup
.test_a
.test_b data:a=3
.test_b data:a=6
.test_c a: 1; b: 2
.test_c a: 3; b: 4
teardown