pytest学习3: pytest fixtures explicit, modular, scalable
Pytest fixtures: explicit, modular, scalable
Pytest 固件:显式、模块化、可扩展
purpose of test fixtures是提供一个固定的基线,在此基础上测试可以可靠地重复执行。Pytest固件比传统的XUnit 的setup/teardown功能提供了显著的改进:
- 固件有明确的名称,通过声明它们在测试函数、模块、类或整个项目中的使用来**。
- 固件以模块化的方式实现,因为每个固件名称触发一个 固件功能 , 可以使用其他固件。
- 固件管理规模从简单的单元扩展到复杂的功能测试,允许根据配置和组件选项参数化固件和测试,或者跨功能、类、模块或整个测试会话范围重复使用固件。
此外,pytest继续支持 经典的Xunit-style setup. 你可以混合这两种样式,根据喜好,逐步从经典样式转移到新样式。你也可以从现有的 unittest.TestCase style 或 nose based 项目开始。
1. fixtures作为函数参数
测试函数可以通过将fixture对象命名为输入参数来接收它们。对于每个参数名,具有该名称的fixture函数提供fixture对象。通过用@pytest.fixture
标记fixture函数来注册fixture函数 . 让我们来看一个简单的独立测试模块,它包含一个fixture和一个使用fixture的测试函数:
# content of ./test_smtpsimple.py
import pytest
@pytest.fixture
def smtp_connection():
import smtplib
return smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
def test_ehlo(smtp_connection):
response, msg = smtp_connection.ehlo()
assert response == 250
assert 0 # for demo purposes
这里, test_ehlo
需要 smtp_connection
固件值。Pytest将发现并调用 @pytest.fixture
标记 smtp_connection
固件函数。运行测试的方式如下:
$ pytest test_smtpsimple.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR
collected 1 item
test_smtpsimple.py F [100%]
================================= FAILURES =================================
________________________________ test_ehlo _________________________________
smtp_connection = <smtplib.SMTP object at 0xdeadbeef>
def test_ehlo(smtp_connection):
response, msg = smtp_connection.ehlo()
assert response == 250
> assert 0 # for demo purposes
E assert 0
test_smtpsimple.py:11: AssertionError
========================= 1 failed in 0.12 seconds =========================
在失败的回溯中,我们看到测试函数是用 smtp_connection
参数、 fixture函数创建的实例smtplib.SMTP()
来调用的。测试功能失败是故意使用了 assert 0
. pytest
以这种方式来调用测试函数:
- pytest找到这个
test_ehlo
因为test_
前缀。这个测试函数需要一个名为smtp_connection
. 通过查找名为smtp_connection
标记的固件函数,找到一个匹配的固件函数. -
smtp_connection()
通过创建实例来调用。 -
test_ehlo(<smtp_connection instance>)
在测试函数的最后一行调用并失败。
请注意,如果你拼错了一个函数参数,或者希望使用一个不可用的参数,你将看到一个错误,其中包含一个可用函数参数列表。
注解
你可以随时发布:
pytest --fixtures test_simplefactory.py
查看可用的固件(带引线的固件 _
仅当添加 -v
选择权。
2. fixtures:a prime example of dependency injection
固件:依赖注入的主要示例
fixture允许测试函数轻松地接收和处理特定的预初始化应用程序对象,而不必关心import/setup/teardown细节。这是一个dependency injection的主要例子,其中fixture函数扮演的是injector(注入), 测试函数是固件对象的consumers(消费者) 。
3. conftest.py
:sharing fixture functions
共享固件功能
如果在实现测试的过程中,你意识到要使用来自多个测试文件的fixture函数,可以将其移动到 conftest.py
文件。你不需要导入要在测试中使用的固件,Pytest会发现并自动获取它。fixture函数的发现从测试类开始,然后是测试模块,然后 conftest.py
文件,最后是内置插件和第三方插件。
你也可以使用 conftest.py
文件去实现 local per-directory plugins.
4. Sharing test data
共享测试数据
如果你想让来自文件的测试数据对你的测试可用,一个很好的方法是将这些数据加载到一个固件中,供测试使用。这利用了pytest的自动缓存机制。
另一个好方法是将数据文件添加到 tests
文件夹中. 这里也有可用的插件社区可以用来帮助管理这方面的testing e.g. pytest-datadir 和 pytest-datafiles.
5. Scope: sharing a fixture instance across tests in a class, module or session
范围:在类、模块或会话中跨测试共享一个fixture实例
依赖于连接性的需要访问网络的fixtures,通常创建成本很高。扩展前面的示例,我们可以给 @pytest.fixture
调用添加一个 scope="module"
参数,引起被装饰的 smtp_connection
固件函数在每个测试模块中只调用一次(默认情况下,每个测试调用一次 function)因此,一个测试模块中的多个测试功能将接收相同的 smtp_connection
固件实例(不必每次都实例化,也就是不用每次都去创建连接访问网络),节省时间提高效率。 对于scope
可能值是: function
, class
, module
, package
或 session
.
下一个示例将fixture函数放入单独的 conftest.py
文件,以便目录中多个测试模块的测试可以访问fixture函数:
# content of conftest.py
import pytest
import smtplib
@pytest.fixture(scope="module")
def smtp_connection():
return smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
固件的名称同样是 smtp_connection
,你可以通过列出名字smtp_connection
作为一个在任何测试或fixture函数的输入参数(在 conftest.py
所在的目录中,或者所在的目录下)来访问它的结果 :
# content of test_module.py
def test_ehlo(smtp_connection):
response, msg = smtp_connection.ehlo()
assert response == 250
assert b"smtp.gmail.com" in msg
assert 0 # for demo purposes
def test_noop(smtp_connection):
response, msg = smtp_connection.noop()
assert response == 250
assert 0 # for demo purposes
我们故意插入失败 assert 0
语句以检查正在进行的操作,现在可以运行测试:
$ pytest test_module.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR
collected 2 items
test_module.py FF [100%]
================================= FAILURES =================================
________________________________ test_ehlo _________________________________
smtp_connection = <smtplib.SMTP object at 0xdeadbeef>
def test_ehlo(smtp_connection):
response, msg = smtp_connection.ehlo()
assert response == 250
assert b"smtp.gmail.com" in msg
> assert 0 # for demo purposes
E assert 0
test_module.py:6: AssertionError
________________________________ test_noop _________________________________
smtp_connection = <smtplib.SMTP object at 0xdeadbeef>
def test_noop(smtp_connection):
response, msg = smtp_connection.noop()
assert response == 250
> assert 0 # for demo purposes
E assert 0
test_module.py:11: AssertionError
========================= 2 failed in 0.12 seconds =========================
你看这两个 assert 0
失败,更重要的是,你也可以看到相同的(module-scoped模块范围) smtp_connection
对象被传递到两个测试函数中,因为pytest在回溯中显示了传入的参数值。因此,两个测试函数使用 smtp_connection
像单个实例一样快速运行,因为它们重用同一个实例。
如果你决定希望有一个会话范围装饰的 smtp_connection
实例, 例如,你可以简单地声明它:
@pytest.fixture(scope="session")
def smtp_connection():
# the returned fixture value will be shared for
# all tests needing it
...
最后, class
作用域将在每个测试class中调用fixture一次 .
注解
pytest一次只缓存一个fixture实例。这意味着当使用参数化固件时,pytest可以在给定的范围内多次调用固件。
package
scope(experimental)
3.7 新版功能.
在Pytest 3.7中, package
范围已引入。当最后一次package测试结束时,Package-scoped fixtures 完成。
警告
考虑到该功能时 实验性的 ,如果在将来的版本中发现隐藏的角落情况或此功能的严重问题,则可能将其删除。
请谨慎使用此新功能,并确保报告你发现的任何问题。
6. Higher-scoped fixtures are instantiated first
待续。。。