pytest fixture

fixture

fixture是pytest特有的功能,它用pytest.fixture标识,定义在函数前面。在你编写测试函数的时候,你可以将此函数名称做为传入参数,pytest将会以依赖注入方式,将该函数的返回值作为测试函数的传入参数。
如下面的例子,我们首先定义了hello函数,并将其标记为fixture,在test_string函数中,我们将它作为入参使用。

1
2
3
4
5
6
@pytest.fixture()
def hello():
return "hello"

def test_string(hello):
assert hello == "hello", "fixture should return hello"

fixture提供给开发者多种可能性,可以把它当做对象创建者,充当测试开始前的准备函数(类似setup_function),还能为测试函数提供多种不同参数fixture对象。

1. 准备函数: usefixtures

在测试的时候,我们有时需要需要setup_function和teardown_function等系列函数来完成测试函数的初始化以及清理工作。
fixture提供了更加灵活的方式,来完成这个工作。
usefixtures标记应用在函数前,表示在开始测试前应用该函数但不需要其返回值。
下面例子创建一个临时目录,并将其设为当前目录。

1
2
3
4
5
6
7
8
9
10
11
@pytest.fixture()
def cleandir():
newpath = tempfile.mkdtemp()
os.chdir(newpath)

@pytest.mark.usefixtures("cleandir")
class TestDirectoryInit:
def test_cwd_starts_empty(self):
assert os.listdir(os.getcwd()) == []
with open("myfile", "w") as f:
f.write("hello")

我们也可以使用逗号分割,使用多个fixture。
pytest.mark.usefixtures(“cleandir”, “anotherfixture”)

2. 销毁函数: addfinalizer

只初始化可能还不够,我们还需要释放函数,fixture通过addfinallizer注册释放函数。
下面例子将在测试函数完成后,释放smtp对象。

1
2
3
4
5
6
7
8
@pytest.fixture()
def smtp(request):
smtp = smtplib.SMTP("smtp.gmail.com")
def fin():
print ("teardown smtp")
smtp.close()
request.addfinalizer(fin)
return smtp # provide the fixture value

获取调用函数信息: request

使用fixture标记函数后,函数将默认接入一个request参数,它将包含使用该fixture函数的信息,详情可参考这里。
这使得我们可以更加灵活的根据不同的函数来决定创建不同的对象以及释放函数。
例如下面例子,我们检查测试函数对应的module里的变量值,以此来决定创建和释放函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# content of conftest.py
@pytest.fixture()
def smtp(request):
server = getattr(request.module, "smtpserver", "smtp.gmail.com")
need_finalize = getattr(request.module, "need_finalize", True)
smtp = smtplib.SMTP(server)
def fin():
if need_finalize:
print ("finalizing %s (%s)" % (smtp, server))
smtp.close()
request.addfinalizer(fin)
return smtp

# content of test_anothersmtp.py
smtpserver = "mail.python.org" # will be read by smtp fixture
need_finalize = False
def test_showhelo(smtp):
assert 0, smtp.helo()

3. 参数

有时我们需要全面测试多种不同条件下的一个对象,功能是否符合预期。我们可以通过params参数来指定传入的参数。
下面例子test_mail将执行2次,分别对应smtp fixture的两种不同参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class SMTP:
def __init__(self, smtp, sender, receiver):
self.smtp = smtp
self.sender = sender
self.receiver = receiver
def __del__(self):
self.smtp.close()

@pytest.fixture(params=[["smtp.gmail.com", "from@domain.com", "to@doamin.com"], ["mail.python.org", "from@python.org", "to@python.org"])
def smtp(request):
return SMTP(#param)

def test_mail(smtp):
message = "hello"
assert smtp.sendmail(message)

pytest在使用不同参数运行test时将生成不同的测试id。我们可以通过ids指定其id。

1
2
3
@pytest.fixture(params=[1, 2, 4, 8], ids=["a", "b"])
def param_a(request):
return request.param

fixture的作用域

fixture提供三种作用域,用于指定fixture初始化的规则。

function

1
@pytest.fixture(scope="function")

默认是scode=”function”,表示fixture会为每个function初始化一次。

module

1
@pytest.fixture(scope="module")

scope=”module”表示一个模块fixture只初始化一次,如果一个模块使用多次该fixture,将会使用同个对象。

session

1
@pytest.fixture(scope="session")

scope=”session”,表示全局初始化一次。通常用于全局系统初始化.
可以使用py.test –fixture test_module.py 查看绑定的fixture

fixture的自动执行:autouse

我们有时候需要某些fixture在全局自动执行,如某些全局变量的初始化工作,亦或一些全局化的清理或者初始化函数。这时我们可以使用autouse。
下面例子,在开始测试前我们清理了此前可能残留的文件,接着将程序目录设置为该目录。设置为autouse=True使得函数将默认执行。

1
2
3
4
5
6
work_dir = "/tmp/app"
@pytest.fixture(session="session", autouse=True)
def clean_workdir():
shutil.rmtree(work_dir)
os.mkdir(work_dir)
os.chrdir(work_dir)

总结

fixture的存在使得我们在编写测试函数的准备函数、销毁函数或者多个条件的测试提供了更加灵活的选择。