美文网首页
pytest探究(2):fixtures

pytest探究(2):fixtures

作者: 霍格沃兹测试学院 | 来源:发表于2020-12-25 14:14 被阅读0次

    pytest 的 fixtures 基于 xUnit 的 setup/teardown 风格,可对测试进行前置处理和后置处理,以下图片是对 xUnit 风格的总结,详细可以参考上面链接:

    image

    pytest 的 fixtures 特点如下:

    • fixtures 有显式名字:可从测试函数,模块,类或者整个项目中直接调用 fixture
    • fixtures 采用模块化:每个 fixture 名字都会调用一个 fixture 函数,该函数也可以调用其他 fixtures
    • fixtures 管理从简单的单元扩展到复杂的功能测试,允许根据配置和组件选项对 fixture 和测试进行参数化,或者跨功能、类、模块或整个测试会话范围重用 fixture 。

    pytest 支持 classic xunit-style setup 。可以混合这两种样式,从经典样式递增到新样式。也可以使用现有的 unittest.TestCase style 样式或基于 nose 的项目开始。

    可以使用 @pytest.fixture 装饰器定义 Fixtures ,比如 官方链接 1。Pytest 也提供许多内置的 fixtures,以下是简述,后面会进行详解 :

    capfd

    捕获为文本,输出到文件描述符 “1” 和 “2” 。

    capfdbinary

    Capture, as bytes, output to file descriptors 1 and 2 .

    caplog

    Control logging and access log entries.

    capsys

    Capture, as text, output to sys.stdout and sys.stderr .

    capsysbinary

    Capture, as bytes, output to sys.stdout and sys.stderr .

    cache 1

    Store and retrieve values across pytest runs.

    doctest_namespace

    Provide a dict injected into the docstests namespace.

    monkeypatch

    Temporarily modify classes, functions, dictionaries, os.environ , and other objects.

    pytestconfig

    Access to configuration values, pluginmanager and plugin hooks.

    record_property

    Add extra properties to the test.

    record_testsuite_property

    Add extra properties to the test suite.

    recwarn

    Record warnings emitted by test functions.

    request

    Provide information on the executing test function.

    testdir

    Provide a temporary test directory to aid in running, and testing, pytest plugins.

    tmp_path 1

    Provide a pathlib.Path object to a temporary directory which is unique to each test function.

    tmp_path_factory 1

    Make session-scoped temporary directories and return pathlib.Path objects.

    tmpdir

    Provide a py.path.local object to a temporary directory which is unique to each test function; replaced by tmp_path 1.

    tmpdir_factory

    Make session-scoped temporary directories and return py.path.local objects; replaced by tmp_path_factory 1.

    Fixtures 作为函数参数

    测试函数将 fixtures 命名为输入参数,从而来接收它们。 对于每个参数名,与该参数名同名的 fixtures 函数会提供 fixture 对象。 可以用 @pytest.fixture 来注册 fixture 函数。 比如下面例子:

    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 fixture 的值。 pytest 会发现用 @pytest.fixture 标记的 smtp_connection fixture 函数,运行测试结果如下:

    $ pytest test_smtpsimple.py
    =========================== test session starts ============================
    platform linux -- Python 3.x.y, pytest-5.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:14: AssertionError
    ========================= short test summary info ==========================
    FAILED test_smtpsimple.py::test_ehlo - assert 0
    
    

    从错误信息可以看出,测试函数需要 smtp_connection 参数, fixture 函数创建了 smtplib.SMTP() 实例,因为代码中是 assert 0 ,所以运行会失败,整个调用流程如下:

    1. pytest发现测试函数 test_ehlo ,由于测试函数需要 smtp_connection 参数,存在与这个参数同名的 fixture 函数。
    2. 调用 smtp_connection() 函数创建实例
    3. 最终调用 test_ehlo(<smtp_connection instance>)函数

    如果拼写了错误的函数参数或者这个参数不可用, pytest 会报错,并列出可用的参数。也可以在命令行用 --fixtures 列出所有可用的 fixtures (包括系统内置的 fixture 和用户自定义的 fixture ),考虑以下代码及执行结果:

    
    import pytest
    
    @pytest.fixture()
    def get_10():
        return 10
    
    def test_fixture(get_10):
        print(get_10)
    
    

    使用命令 pytest --fixtures 后,输出结果只截取自己的 fixture ,系统内置的 fixture 没有截取:

    [ image

    image1618×242 6.26 KB](https://ceshiren.com/uploads/default/original/2X/2/22938485954685e0fe5bd382112ecb31c6f52a42.png)

    加上 -v 参数可以展示详细信息,包括 fixture 位置,比如执行 pytest --fixtures -v

    [ image

    image1606×284 7.94 KB](https://ceshiren.com/uploads/default/original/2X/2/29c638da473c15c8d33463cf797ce34e25180a94.png)

    Fixtrues: 依赖注入

    fixtures 允许测试函数接收初始化后的对象,而不必关心导入/设置/清理的详细信息。这就是依赖注入模式,了解 spring 的同学应该不陌生, spring 用它解决了组件的依赖项和组件之间的过度耦合问题,在 pytest 也是同理。有关依赖注入的详细内容,可查看 wiki :

    https://en.wikipedia.org/wiki/Dependency_injection 1

    conftest.py:共享 fixture 函数

    有时想在多个测试文件中使用 fixture 函数,就要把 fixture 函数移动到 conftest.py 文件中。 pytest 会自动发现 conftest.py ,你不用手动导入, 发现规则有前有后,依次是类,测试模块,然后是 conftest.py 文件,内置或者第三方插件。

    conftest.py 还可以实现 local per-directory plugins

    考虑以下目录结构及代码内容:

    image
    # a/conftest.py文件内容
    import pytest
    @pytest.fixture()
    def tmp():
        return 20
    
    # a/a1/conftest.py文件内容
    import pytest
    @pytest.fixture()
    def tmp():
        return 30
    
    # test_a1.py文件内容
    
    def test_print(tmp):
        print(tmp)
    
    # test_a2.py文件内容
    
    def test_print(tmp):
        print(tmp)
    
    

    可以看出, pytest_a1.py 输出结果是 30 , pytest_a2.py 输出结果是 20 ,子目录的 conftest.py 内容会覆盖父目录的 conftest.py :

    [ image

    image2022×341 14.5 KB](https://ceshiren.com/uploads/default/original/2X/f/f4100435115e6911f224f70131932b4e52a952aa.png)[

    image

    image1882×352 14.4 KB](https://ceshiren.com/uploads/default/original/2X/0/0c38a41870e70d82aaa86642edbd4da1a67b59d8.png)

    共享测试数据

    如果让测试数据多文件可用,最好把数据放到 fixture 中,这个方法充分利用了 pytest 的自动缓存机制。另一个好方法是把数据移动到 tests 目录,社区提供一些插件帮助管理,比如 pytest-datadirpytest-datafiles

    范围:在类,模块或者会话中共享 fixture

    @pytest.fixture 参数中添加 scope="module",使其装饰的函数,仅在每个测试模块中实例化一次(默认在每个测试函数前实例化一次),你可以自行打印 fixture 对象,对比区别。比如下面的 smtplib.SMTP ,会创建一个实例,由于它非常依赖网络,所以设置为模块级别可以避免多次创建。 scope 还有更多的值: function , class , module , package 或者 session 。

    接下来的例子把 fixture 放到了 conftest.py 文件,所以目录中的多个测试模块都能访问这个函数:

    # content of conftest.py
    import pytest
    import smtplib
    
    @pytest.fixture(scope="module")
    def smtp_connection():
        return smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
    
    

    你可以在 conftest.py 的同目录,或者子目录的测试文件中调用 fixture ,比如下面代码:

    # 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 可以打印详细信息:

    $ pytest test_module.py
    =========================== test session starts ============================
    platform linux -- Python 3.x.y, pytest-5.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:7: 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:13: AssertionError
    ========================= short test summary info ==========================
    FAILED test_module.py::test_ehlo - assert 0
    FAILED test_module.py::test_noop - assert 0
    ============================ 2 failed in 0.12s =============================
    
    

    assert 0 让 pytest 展示接收到的 smtp_connection 对象,这两个测试函数使用相同的 smtp_connection 对象,即 smtp_connection 只会在整个模块初始化一次。你也可以把 scope 改为 session 级别:

    pytest.fixture(scope="session")
    def smtp_connection():
        # the returned fixture value will be shared for
        # all tests needing it
        ...
    
    

    fixture的作用范围

    session>module>class>function

    • function:每一个函数或方法都会调用

    • class:每一个类调用一次,一个类中可以有多个方法

    • module:每一个 *.py 文件调用一次,该文件内又有多个 function 和 class

    • session:是多个文件调用一次,可以跨 *.py 文件调用,每个 *.py 文件就是 module

    注意:
    Pytest 同一时间只会缓存一个 fixture ,当使用参数化的 fixture 时, pytest 会在范围内多次调用 fixture 。 pytest3.7 有包范围,由于是测试版,不建议使用。

    动态范围
    有时,你不想改写代码,做到动态的改变 fixture 范围。可以定义函数,将之传递给 fixture 的 scope 参数,在 fixture 定义的时候会自动执行这个函数。这个函数必须有两个参数 - fixture_name 字符串 和 config 配置对象。

    比如下面生成 docker 容器函数 ,你可以使用命令行参数控制范围:

    # conftest.py 内容
    from time import sleep
    
    import pytest
    
    def determine_scope(fixture_name, config):
        # 如果发现参数是 --keep-containers
        if config.getoption("--scope_session", None):
            return "session"
        # 返回函数级别
        return "function"
    
    @pytest.fixture(scope=determine_scope)
    def create_value():
        sleep(5)
    
    def pytest_addoption(parser):
        parser.addoption("--scope_session", action="store", default=None,
                         help="None")
    
    # test_a1内容
    
    def test_a1(create_value):
        print('a1')
    
    def test_a2(create_value):
        print('a2')
    
    def test_a3(create_value):
        print('a3')
    
    

    参考以下两种运行结果,加入参数的话, fixture 的范围会被更改成 session 级别, 3 个 test 只会初始化一次 fixture 函数,运行时间为 5s 左右:

    [ image

    image1877×484 18 KB](https://ceshiren.com/uploads/default/original/2X/b/bf497d8af7140cdbc4a4f84c03323e1048082283.png)

    如果没有参数, fixture 的范围会被更改成 function 级别, 3 个 test 会初始化三次 fixture 函数,运行时间为 15s 左右:

    image

    (文章来源有霍格沃兹测试学院)

    更多技术文章点击获取http://qrcode.testing-studio.com/f?from=jianshu&url=https://ceshiren.com/t/topic/3822

    相关文章

      网友评论

          本文标题:pytest探究(2):fixtures

          本文链接:https://www.haomeiwen.com/subject/yodsnktx.html