Fixture概述
说到测试框架自然要说到setup
和teardown
两个方法.
setup
是用来做准备操作.一般用来初始化资源.teardown
是用来做收尾操作.一般用于释放资源.
pytest的setup
和teardown
是利用`@pytest.fixture`这个注释来完成的.不仅可以完成初始化操作,初始化后如果有数据需要给用例使用也是非常方便!
fixture方法原型如下:1
2def fixture(scope="function", params=None, autouse=False, ids=None, name=None):
"""Decorator to mark a fixture factory function.
各个参数含义分别如下:
scope
:执行初始化方法的作用域params
:设置初始化方法要传递的参数,列表类型autouse
:默认为False
如果设置为True
则所有的用例都会激活使用它,否则需要显示激活。ids
:参数对应的id
列表,如果不指定则会自动生成name
:定义fixture
名称,默认是被装饰的方法名称。
SetUp
概述
通过`@pytest.fixture()` 注释会在执行测试用例之前初始化操作.然后直接在测试用例的方法中就可以拿到初始化返回的参数(参数名要和初始化的方法名一样)
实践案例
创建test_fixture.py
模块,代码如下:
1 | import pytest |
执行命令
1 | λ pytest -q -s E:\code\learning_python\pytest_test\test_fixture.py |
根据上面的执行结果可以看出,init_data
已经被执行了两次,默认的作用域为单个测试用例方法,如果想修改作用域,则需要用到参数scope
来指定作用域。
Scope
scope
作用域主要包括以下作用域:
module
(模块):作用于一个模块内的所有class
和def
,对于所有class
和def
,setup
和teardown
只执行一次class
(类):作用于一个class
内中的所有test
,所有用例只执行一次setup
,当所有用例执行完成后,才会执行teardown
session
(会话):function
(方法):作用于单个测试用例,若用例没有执行(如被skip
了)或失败了,则不会执行teardown
package
(包):作用于包,该功能还是实验性的,谨慎使用。
上面的案例将作用域改为class
后,再执行测试,此时再执行初始化就执行一次了。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16import pytest
class TestClass():
@pytest.fixture(scope="class")
def init_data(self):
print('init data...')
return 5
def test_add1(self,init_data):
print('test add 1')
assert init_data == 5
def test_add2(self,init_data):
print('test add 2')
assert init_data == 5
scope优先级
在功能方法请求中,更高范围的fixture
(比如会话)比低范围的fixture
(比如函数或类)先被实例化。相同范围的fixture
的相对顺序与测试函数中声明的顺序一致,并且在fixture
之间的依赖关系中保持一致。
实践案例
test_scope.py1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28import pytest
def s1():
print('s1')
pass
def m1():
print('m1')
pass
def f1(tmpdir):
print('f1')
pass
def f2():
print('f2')
pass
def test_foo(f1, m1, f2, s1):
print('test_foo')
执行结果
1 | λ pytest -s -q E:\code\learning_python\pytest_test\test_higher_scope.py |
说明
s1
:是作用域最高的fixture
(会话)。m1
:是第二高作用域fixture
(模块)。tmpdir
:是一个函数作用域的fixture
,f1
需要它:因为它是f1
的依赖项,所以需要在此时实例化它。f1
:test_foo
参数列表中的第一个函数作用域夹具。f2
:是test_foo
参数列表中最后一个函数作用域的fixture
。
执行结果1
2
3
4
5
6pytest -q -s E:\code\learning_python\pytest_test\test_fixture.py
init data...
test add 1
.test add 2
.
2 passed in 0.15 seconds
TearDown
- 在pytest中实现
teardown
有两种方式,一种是使用yield
关键字,另外一种是利用请求上下文对象的addfinalizer
方法来注册完成函数。 - 需要注意一下,如果在设置代码,即
yield
关键字之前,期间发生异常,则不会调用teardown
代码,但是addfinalizer
依旧会执行tearndown
内容。
实践案例
test_fixture.py1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20import pytest
class TestClass():
def init_data(request):
print('init data...')
def teardown():
print('finished init data!')
request.addfinalizer(teardown)
return 5
#测试用例参数中引用初始化方法
def test_add1(self,init_data):
print('test add 1')
assert init_data == 5
def test_add2(self,init_data):
print('test add 2')
assert init_data == 5
在之前的案例中,我们补充使用了addfinalizer
方法来注册完成方法teardown
。
执行结果:1
2
3
4
5
6
7λ pytest -s -q E:\code\learning_python\pytest_test\test_fixture_setup.py
init data...
test add 1
.test add 2
.finished init data!
2 passed in 0.02 seconds
fixture配置文件——conftest.py
如果在实现测试过程中,如果多个测试文件需要使用同一个 fixture
,那么可以将它移动到conftest.py
文件。您不需要导入您想在测试中使用的fixture
,它会被pytest
自动发现。
fixture
函数的发现从测试类开始,然后是测试模块,然后是conftest.py
文件,最后内置和第三方插件。
实践案例
创建模块 conftest.py
1 | import pytest |
将前面的test_fixture
模块改写如下,去掉了之前在模块内部定义的fixture
1
2
3
4
5
6
7
8
9
10
11import pytest
class TestClass():
def test_add1(self,init_data):
print('test add 1')
assert init_data == 5
def test_add2(self,init_data):
print('test add 2')
assert init_data == 5
执行结果
1 | λ pytest -s -q E:\code\learning_python\pytest_test\test_fixture_setup.py |
可以看到完成了初始化,同理,继续创建新的测试模块,在测试方法中传入对应的fixture
方法,依旧可以执行fixture
方法。
注意:fixture
配置文件名称必须是conftest.py
否则会找不到 fixture
方法
参数化的Fixture
概述
fixture
函数可以参数化,在这种情况下,它们将被多次调用,每次执行一组相关测试,即依赖于这个fixture
的测试,测试函数通常不需要知道它们的重新运行。fixture
参数化可以用于一些有多种方式配置的功能测试。
实践案例
扩展前面的例子,我们可以标记fixture
来创建两个smtp
连接实例,这将导致使用fixture
运行两次的所有测试。fixture
功能通过特殊的请求对象访问每个参数:
conftest.py1
2
3
4
5
def smtp_connection_multi(request):
import smtplib
return smtplib.SMTP(request.param,587,timeout=5)
说明:在参数中增加一个参数param
将要测试的参数用列表存储。同时在方法中增加request
参数,参数遍历是通过request.param
来实现。
test_smtpsimple.py
1 | def test_ehlo_multi(smtp_connection_multi): |
执行结果1
2
3
4
5
6
7
8
9
10
11λ pytest -s E:\code\learning_python\pytest_test\test_smtpsimple.py
============================= test session starts =============================
platform win32 -- Python 3.5.0, pytest-3.8.1, py-1.5.3, pluggy-0.7.1
rootdir: E:\, inifile:
collected 2 items
..\..\..\code\learning_python\pytest_test\test_smtpsimple.py 250
.250
.
========================== 2 passed in 22.96 seconds ==========================
ids
- 上面案例中两个测试函数每个都运行了两次,而且是针对不同的
smtp
实例。pytest
将建立一个字符串,它是参数化fixture
中每个fixture
值的测试ID
, - 使用
--collect-only
运行pytest
会显示生成的ID
。
1 | λ pytest --collect-only E:\code\learning_python\pytest_test\test_smtpsimple.py |
在上面的结果中,test_ehlo[smtp.qq.com]
和test_ehlo[mail.python.org]
,这些ID
可以与-k
一起使用来选择要运行的特定实例,还可以在发生故障时识别特定实例。
1 | λ pytest --collect-only -k smtp.gmail.com E:\code\learning_python\pytest_test\test_smtpsimple.py |
实践案例
数字、字符串、布尔值和None
将在测试ID
中使用其通常的字符串表示形式,对于其他对象,pytest
会根据参数名称创建一个字符串,可以通过使用ids
关键字参数来自定义用于测试ID
的字符串。
test_ids.py
1 | import pytest |
执行结果1
2
3
4
5
6
7
8
9
10λ pytest --collect-only E:\code\learning_python\pytest_test\test_ids.py
============================= test session starts =============================
platform win32 -- Python 3.5.0, pytest-3.8.1, py-1.5.3, pluggy-0.7.1
rootdir: E:\, inifile:
collected 2 items
<Module 'code/learning_python/pytest_test/test_ids.py'>
<Function 'test_a[spam]'>
<Function 'test_a[ham]'>
======================== no tests ran in 0.19 seconds =========================
从上面的执行结果可以看出,此时的id
为我们设定的id
而不是自动生成的id
。我们可以使用参数-k
来指定执行某一个参数
1 | λ pytest -k ham --collect-only E:\code\learning_python\pytest_test\test_ids.py |
从上面的执行结果我们可以理解,ids
和param
其实是一种key:value
的键值对关系。-k
参数其实就是key
的缩写。我们继续扩展上面的案例,代码如下:
test_ids.py
1 |
|
说明:
- 上面代码中,我们新建了一个方法
idfn
,该方法的作用是接收fixture
值,判断当值为0
时将返回eggs
。 - 在新建的
fixture
中引用idfn
来作为ids
参数,从而实现根据param
值来改变ids
的值。
运行结果:
1 | λ pytest --collect-only E:\code\learning_python\pytest_test\test_ids.py |