pytest-Fixture

pytest-Fixture

1. 简介

fixture 是在测试函数运行前后,由pytest 执行的外壳函数。fixture 可以定制,以满足不同的测试需求,可定制包括传入测试中的数据集、配置测试前后的setup\teardown、为测试提供批量数据源,等等。

2. 示例

先来举个🌰

import pytest

@pytest.fixture()
def some_data():
    return 22

def test_some_data(some_data):
    assert some_data == 22

运行结果

 $ pytest test_fixtures.py::test_some_data
================================================ test session starts =====================================
platform darwin -- Python 3.7.8, pytest-6.1.2, py-1.9.0, pluggy-0.13.1
rootdir: xxx
plugins: html-3.0.0, metadata-1.11.0, asyncio-0.14.0, repeat-0.9.1
collected 1 item

test_fixtures.py .                                                                                                                                                         [100%]

================================================ 1 passed in 0.02s ======================================

从上面的例子可以看出,装饰器 @pytest.fixture() 是用来声明一个函数为一个fixture。如果测试函数的参数列表中包含fixture 名,那pytest 在执行测试的时候就会检测到,并在执行测试函数之前运行fixture。

3. 全局范围内共享fixture

fixture 可以放在单独的测试文件里,但是如果你希望这是一个公用的fixture ,可以在公共目录下新建一个 conftest.py 文件,把需要公共使用的 fixture 编写在这个文件里面,这样在公共目录下的测试case都可以共享其中的fixture。

尽管 conftest.py 格式看起来是Python模块,但是它不能被在测试代码中被导入。 import conftest 这样的语法是不允许出现的。因为 conftestp.py 是被视作pytest 的一个本地插件使用的。

4. 使用详情

4.1 执行配置和销毁逻辑

使用fixture的好处可以省去setup\teardown的步骤,举个连接数据库的例子。

import pytest

# 伪代码
@pytest.fixture()
def tasks_db():
    # 首先测试开始前连接数据库
    连接数据库的代码...
    # 返回测试代码执行测试
    yield
    # 测试结束后断开连接
    断开连接数据库的代码

yield 之前的代码可以视作 **setup **的过程,而 yield 会返回测试代码执行正常的测试,在测试代码执行完毕后,接着会继续执行 yield 之后的代码,视作 teardown 的过程。

4.2 回溯fixture执行过程

在上面的使用fixture 进行测试的例子中,我们都看不到fixture 的执行过程
在这里插入图片描述

但是有的时候我们又希望在执行过程中看到fixture 的详细信息,这时候需要使用 --setup-show 参数了。
在这里插入图片描述

展示的信息正如我们的预期一样,我们的测试夹在SETUP 和 TEARDOWN 中间。S 和 F 代表的 fixture 的作用范围,下面来介绍 fixture 的作用范围。

4.3 fixture作用范围

在创建 fixture 时,有一个 scope 的可选参数,用于控制 fixture 执行配置和销毁逻辑的频率(范围)。
这个参数有四个可选值

  • function 函数级别(默认)
  • class 类级别
  • module 模块级别
  • session 会话级别

下面来介绍各个级别的参数
scope='function'
函数级别的fixture ,每个测试函数只需要运行一次,配置的代码在测试运行前执行,销毁代码在测试运行后执行。

scope='class'
类级别的fixture ,每个测试只需要运行一次,无论测试类中有多少个测试方法,都会共享这个fixture

scope='module'
模块级别的fixture ,每个模块都只需要运行一次,无论模块中有多少个测试函数、类、或其他 fixture 都可以共享这个模块级别的 fixture

scope='session'
会话级别的fixture , 每次会话只运行一次,一次pytest 会话中的所有内容都共享这个fixture

import pytest

@pytest.fixture(scope='function')
def func_scope():
    """A function scope fixture."""


@pytest.fixture(scope='module')
def mod_scope():
    """A module scope fixture."""


@pytest.fixture(scope='session')
def sess_scope():
    """A session scope fixture."""


@pytest.fixture(scope='class')
def class_scope():
    """A class scope fixture."""


def test_1(sess_scope, mod_scope, func_scope):
    """Test using session, module, and function scope fixtures."""


def test_2(sess_scope, mod_scope, func_scope):
    """Demo is more fun with multiple tests."""

@pytest.mark.usefixtures('class_scope')
class TestSomething():
    """Demo class scope fixtures."""

    def test_3(self):
        """Test using a class scope fixture."""

    def test_4(self):
        """Again, multiple tests are more fun."""

下面来看下运行效果,使用 --setup-show 参数来查看每个fixture 的执行过程

$ pytest --setup-show test_scope.py
================================= test session starts ===============================================================================
platform darwin -- Python 3.7.8, pytest-6.1.2, py-1.9.0, pluggy-0.13.1
rootdir: xxx
plugins: html-3.0.0, metadata-1.11.0, asyncio-0.14.0, repeat-0.9.1
collected 4 items

test_scope.py
SETUP    S sess_scope
    SETUP    M mod_scope
        SETUP    F func_scope
        test_scope.py::test_1 (fixtures used: func_scope, mod_scope, sess_scope).
        TEARDOWN F func_scope
        SETUP    F func_scope
        test_scope.py::test_2 (fixtures used: func_scope, mod_scope, sess_scope).
        TEARDOWN F func_scope
      SETUP    C class_scope
        test_scope.py::TestSomething::test_3 (fixtures used: class_scope).
        test_scope.py::TestSomething::test_4 (fixtures used: class_scope).
      TEARDOWN C class_scope
    TEARDOWN M mod_scope
TEARDOWN S sess_scope

================================= 4 passed in 0.05s ================================================================================

从上面的测试结果中不难看出,出现了 S M F 等级别的fixture。
是因为,作用范围虽然是由 fixture 自身定义,但还是要强调 scope 参数是在定义 fixture 时定义的,而不是在调用 fixture 时定义的,因此使用 fixture 的测试函数是无法改变 fixture 的作用范围的。
fixture 只能使用同级别的 fixture,或者比自己更高级别的 fixture。如函数级别的 fixture 可以使用同级别的 fixture ,也可以使用类级别、会话级别等。但是不能反过来。

4.4 使用usefixtures指定fixture

上面的例子中(除4.3)都是直接在测试函数的参数列表里指定使用的fixture,还有一种使用方法,就是用 @pytest.mark.usefixtures('fixture1, fixture2') 这样的装饰器来指定使用的 fixture。

import pytest

@pytest.mark.usefixtures('class_scope')
class TestSomething():
    """Demo class scope fixtures."""

    def test_3(self):
        """Test using a class scope fixture."""

    def test_4(self):
        """Again, multiple tests are more fun."""

但是两者还是有区别的,usefixtures是无法使用fixture 的返回值的,在参数列表中指定 fixture 是可以使用 fixture 的返回值的

4.5 autouse选项

我们可以通过 autouse=Ture 选项来使作用域内的测试函数都执行该 fixture 。一般情况下,需要运行多次且不依赖任何系统状态/外部数据的测试使用较多。

import pytest
import time


@pytest.fixture(autouse=True, scope='session')
def footer_session_scope():
    yield
    now = time.time()
    print('--')
    print('finished : {}'.format(time.strftime('%d %b %X', time.localtime(now))))
    print('-----------------')


@pytest.fixture(autouse=True)
def footer_function_scope():
    start = time.time()
    yield
    stop = time.time()
    delta = stop - start
    print('\ntest duration : {:0.3} seconds'.format(delta))


def test_1():
    time.sleep(1.25)


def test_2():
    time.sleep(2.5)

上面这段代码,我们希望在每次测试会话结束后都打印结束的日期和时间,并打印出每个测试所使用的的时间。
下面为测试结果

$ pytest -vs test_autouse.py
============================= test session starts ===============================================================================
platform darwin -- Python 3.7.8, pytest-6.1.2, py-1.9.0, pluggy-0.13.1 -- /Library/Frameworks/Python.framework/Versions/3.7/bin/python3.7
cachedir: .pytest_cache
metadata: {'Python': '3.7.8', 'Platform': 'Darwin-20.1.0-x86_64-i386-64bit', 'Packages': {'pytest': '6.1.2', 'py': '1.9.0', 'pluggy': '0.13.1'}, 'Plugins': {'html': '3.0.0', 'metadata': '1.11.0', 'asyncio': '0.14.0', 'repeat': '0.9.1'}, 'JAVA_HOME': '/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home'}
rootdir: xxx
plugins: html-3.0.0, metadata-1.11.0, asyncio-0.14.0, repeat-0.9.1
collected 2 items

test_autouse.py::test_1 PASSED
test duration : 1.25 seconds

test_autouse.py::test_2 PASSED
test duration : 2.5 seconds
--
finished : 01 Mar 16:05:11
-----------------


==================================== 2 passed in 3.81s ================================================================================

但是除非你可以肯定测试代码不依赖任何,也不需要任何数据的情况下,否则尽量少使用这种方式。

4.6 为fixture重命名

fixture 的名字有的时候可能会很长,我们目前使用fixture 都是使用定义时候的函数名字,但是当这个名字很长但是有意义你不想改变的时候,我们可以为该fixture 重命名,使其使用起来更方便
重命名的方式也很简单,就是在定义的时候加上参数 name@pytest.fixture(name='xxx')

import pytest


@pytest.fixture(name='lue')
def ultimate_answer_to_life_the_universe_and_everything():
    return 42


def test_everything(lue):
    assert lue == 42

这样我们就以一个简短明了的名字使用了fixture ,但是该fixture 还是保持的它那个有意义且很明确的名字。在测试执行的时候也会变成这简短明了的名字

$ pytest --setup-show test_rename_fixture.py
================================= test session starts ===============================================================================
platform darwin -- Python 3.7.8, pytest-6.1.2, py-1.9.0, pluggy-0.13.1
rootdir: xxx
plugins: html-3.0.0, metadata-1.11.0, asyncio-0.14.0, repeat-0.9.1
collected 1 item

test_rename_fixture.py
        SETUP    F lue
        test_rename_fixture.py::test_everything (fixtures used: lue).
        TEARDOWN F lue

================================== 1 passed in 0.03s ================================================================================

当我们想查看 lue 来自哪里的时候,我们可以使用 --fixtures 参数来查看,由于输出内容很多,下面展示需要的简短的信息

$ pytest --fixtures test_rename_fixture.py
...
------------------------------- fixtures defined from test_rename_fixture --------------------------------------------------------------------
lue
    Return ultimate answer.
================================== no tests ran in 0.03s ==============================================================================

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值