欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

pytest学习3: pytest fixtures explicit, modular, scalable

程序员文章站 2022-03-11 16:17:01
...

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 以这种方式来调用测试函数:

  1. pytest找到这个 test_ehlo 因为 test_ 前缀。这个测试函数需要一个名为 smtp_connection . 通过查找名为 smtp_connection 标记的固件函数,找到一个匹配的固件函数.
  2. smtp_connection() 通过创建实例来调用。
  3. 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可能值是: functionclassmodulepackagesession .

下一个示例将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


待续。。。

相关标签: pytest