一、关于夹具

1、夹具介绍

简单来说,pytest中的夹具就是用来实现测试前的环境准备,提供测试数据和测试后的环境清理动作。类似于unittest框架里的setup(前置处理),teardown(后置处理)。

常用在测试环境搭建和销毁,测试数据的共享,登录登出操作,数据库连接和关闭,浏览器打开关闭等操作。

2、夹具特点

  • fixture固定装置,也叫夹具,下文都称呼为夹具。
  • 使用 @pytest.fixture 可以将一个函数标记成夹具。
  • 夹具本质上是函数,测试函数显示调用来使用夹具。
  • 夹具的名称不要用test开头。
  • 夹具可以定义在conftest.py文件中。
  • 可以在测试函数,测试类,模块,甚至session会话级别使用夹具。
  • 测试函数可以使用一个或者多个夹具,夹具也可以使用夹具,这才是fixture夹具功能真正强大的地方。
  • pytest查找夹具的顺序:测试类>>测试模块>> conftest.py 文件>>内置插件和第三方插件。

3、夹具的简单使用

新建 test_fixture.py 文件如下:

import pytest
@pytest.fixture
def myfixture():
    print("this is myfixture")
    return True


def test_one(myfixture):
    assert myfixture is True

运行命令:pytest test_fixture.py -vs

(venv) C:\Users\057776\PycharmProjects\pytest-demo\testfixture>pytest test_fixture.py -vs
=================================== test session starts ===================================
platform win32 -- Python 3.8.8, pytest-6.2.3, py-1.10.0, pluggy-0.13.1 -- e:\programs\python\python38\python.exe
cachedir: .pytest_cache
metadata: {'Python': '3.8.8', 'Platform': 'Windows-10-10.0.19041-SP0', 'Packages': {'pytest': '6.2.3', 'py': '1.10.0', 'pluggy': '0.13.1'}, 'Plugins': {'html': '3.1.1', 'metadata': '1.11.0', 'rerunfailures': '9.1.1', 'assume': '2.2.0', 'requests-mock': '1.7.0'}, 'JAVA_HOME': 'C:\\Program Files\\Java\\jdk1.8.0_271', 'foo': 'bar'}

rootdir: C:\Users\057776\PycharmProjects\pytest-demo\testfixture
plugins: html-3.1.1, metadata-1.11.0, rerunfailures-9.1.1, assume-2.2.0, requests-mock-1.7.0
collected 1 item                                                                                                                                                    

test_fixture.py::test_one this is myfixture                PASSED

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

(venv) C:\Users\057776\PycharmProjects\pytest-demo\testfixture>

说明:

测试函数test_one 使用myfixture作为参数,pytest 运行时候,会先查找并运行它参数的同名夹具myfixture,打印出了"this is my fixture",并且捕获到了夹具的返回值 True。

夹具执行过程(官网说明):

在基本层面上,测试函数通过将它们声明为参数来请求它们所需的夹具。

当 pytest 开始运行测试时,它会查看该测试函数签名中的参数,然后搜索与这些参数具有相同名称的夹具。一旦 pytest 找到它们,它就会运行这些固定装置,捕获它们返回的内容(如果有的话),并将这些对象作为参数传递给测试函数。

二、定义夹具

使用@pytest.fixture装饰器可以将测试函数标记成夹具,可以在测试用例文件中定义夹具,也可以在conftest.py文件中定义夹具,推荐使用conftest.py文件定义夹具。

1、fixture参数

fixture它有5个参数,分别是scope, params, autouse, ids, name。

@fixture(fixture_function=None*scope='function'params=Noneautouse=Falseids=Nonename=None)

scope:

作用域参数,他有4个级别分别是 function,class,module,session,默认function。

  • function:函数级别,默认范围,夹具在测试结束时被销毁。
  • class:类级别,夹具在类中最后一个测试结束时被销毁。
  • module:模块级别,夹具在.py文件中最后一个测试结束时被销毁
  • session:会话级别,夹具在测试会话结束时被销毁。

params:

一个可选的参数列表,默认值为None,可以用来实现 参数化,参数化的时候这里需要用到一个参数request,使用“request.param”来接受请求参数列表,进而实现参数化。

ids:

字符串列表,与params配合使用,如果没有提供 id,它们将从参数中自动生成。

autouse:

默认值False ,需要显式引用来激活夹具。如果为TRUE,则所有测试函数都可以使用该夹具(慎用)。

name:

夹具名称默认取的是被定义成夹具的函数名称,可以修改这个参数来修改夹具名称,需要注意的是,只能使用修改后的夹具名,不能使用原来的夹具函数名。

2、yield和return的区别

夹具中可以使用return,yield关键字为测试函数提供值,推荐使用yield关键字,他们的区别如下:

  • yield返回值后,后面的代码还会继续运行
  • return返回值后,后面的代码不会继续运行

三、使用夹具

1、function函数级别,将夹具名作为测试函数参数的入参(显示调用)

新建conftest.py配置文件如下,定义一个function级别的夹具。

import pytest


@pytest.fixture(scope='function')
def myfixture_function():
    print("开始加载function级别夹具")
    yield 100
    print("开始退出function级别夹具")

修改test_fixture.py文件如下:

def test_one(myfixture_function):
    assert myfixture_function == 100

文件目录结构如下:

进入testfixture目录,运行命令:pytest -vs

Microsoft Windows [版本 10.0.19044.1889]
(c) Microsoft Corporation。保留所有权利。

(venv) C:\Users\057776\PycharmProjects\pytest-demo\testfixture>pytest -vs
=================================== test session starts ===================================
platform win32 -- Python 3.8.8, pytest-6.2.3, py-1.10.0, pluggy-0.13.1 -- e:\programs\python\python38\python.exe
cachedir: .pytest_cache
metadata: {'Python': '3.8.8', 'Platform': 'Windows-10-10.0.19041-SP0', 'Packages': {'pytest': '6.2.3', 'py': '1.10.0', 'pluggy': '0.13.1'}, 'Plugins': {'html': '3.1.1', 'metadata': '1.11.0', 'rerunfailures': '9.1.1', 'assume': '2.2.0', 'requests-mock': '1.7.0'}, 'JAVA_HOME': 'C:\\Program Files\\Java\\jdk1.8.0_271', 'foo': 'bar'}

rootdir: C:\Users\057776\PycharmProjects\pytest-demo\testfixture
plugins: html-3.1.1, metadata-1.11.0, rerunfailures-9.1.1, assume-2.2.0, requests-mock-1.7.0
collected 1 item                                                                                                                                                    

test_fixture.py::test_one 开始加载function级别夹具
PASSED开始退出function级别夹具


=================================== 1 passed in 0.05s ===================================

(venv) C:\Users\057776\PycharmProjects\pytest-demo\testfixture>

可知,function级别的夹具在显示调用该夹具的测试用例执行完后销毁。

2、class类级别,使用mark标记装饰器@pytest.mark.userfixtures

语法糖:

pytest.mark.usefixtures(*args)

*args – The names of the fixture to use, as strings.

看到装饰器的参数就能晓得,usefixtures可以和一个或者多个fixture配合使用。

修改conftest.py配置文件如下,新增一个class级别的夹具。

import pytest


@pytest.fixture(scope='function')
def myfixture_function():
    print("开始加载function级别夹具")
    yield 100
    print("开始退出function级别夹具")


@pytest.fixture(scope='class')
def myfixture_class():
    print("开始加载class级别夹具")
    yield 200
    print("开始退出class级别夹具")

修改test_fixture.py文件如下:

import pytest


def test_one(myfixture_function):
    assert myfixture_function == 100


@pytest.mark.usefixtures('myfixture_class')
class TestFixture:

    def test_two(self, myfixture_function):
        assert myfixture_function == 100

    def test_three(self):
        pass

进入testfixture目录,运行命令:pytest -vs

(venv) C:\Users\057776\PycharmProjects\pytest-demo\testfixture>pytest -vs
=================================== test session starts ===================================
platform win32 -- Python 3.8.8, pytest-6.2.3, py-1.10.0, pluggy-0.13.1 -- e:\programs\python\python38\python.exe
cachedir: .pytest_cache
metadata: {'Python': '3.8.8', 'Platform': 'Windows-10-10.0.19041-SP0', 'Packages': {'pytest': '6.2.3', 'py': '1.10.0', 'pluggy': '0.13.1'}, 'Plugins': {'html': '3.1.1', 'metadata': '1.11.0', 'rerunfailures': '9.1.1', 'assume': '2.2.0', 'requests-mock': '1.7.0'}, 'JAVA_HOME': 'C:\\Program Files\\Java\\jdk1.8.0_271', 'foo': 'bar'}

rootdir: C:\Users\057776\PycharmProjects\pytest-demo\testfixture
plugins: html-3.1.1, metadata-1.11.0, rerunfailures-9.1.1, assume-2.2.0, requests-mock-1.7.0
collected 3 items                                                                                                                                                   

test_fixture.py::test_one 开始加载function级别夹具
PASSED开始退出function级别夹具

test_fixture.py::TestFixture::test_two 开始加载class级别夹具
开始加载function级别夹具
PASSED开始退出function级别夹具

test_fixture.py::TestFixture::test_three PASSED开始退出class级别夹具


=================================== 3 passed in 0.07s ===================================

(venv) C:\Users\057776\PycharmProjects\pytest-demo\testfixture>

可知,class级别的夹具在测试类中所有测试用例执行完后销毁。

3、module模块级别,使用pytestmark

修改conftest.py配置文件如下,新增一个module级别的夹具。

import pytest


@pytest.fixture(scope='function')
def myfixture_function():
    print("开始加载function级别夹具")
    yield 100
    print("开始退出function级别夹具")


@pytest.fixture(scope='class')
def myfixture_class():
    print("开始加载class级别夹具")
    yield 200
    print("开始退出class级别夹具")


@pytest.fixture(scope='module')
def myfixture_module():
    print("开始加载module级别夹具")
    yield 300
    print("开始退出module级别夹具")

修改test_fixture.py文件如下:

import pytest

pytestmark = pytest.mark.usefixtures('myfixture_module')


def test_one(myfixture_function):
    assert myfixture_function == 100


@pytest.mark.usefixtures('myfixture_class')
class TestFixture:

    def test_two(self, myfixture_function):
        assert myfixture_function == 100

    def test_three(self):
        pass

进入testfixture目录,运行命令:pytest -vs

(venv) C:\Users\057776\PycharmProjects\pytest-demo\testfixture>pytest -vs
=================================== test session starts ===================================
platform win32 -- Python 3.8.8, pytest-6.2.3, py-1.10.0, pluggy-0.13.1 -- e:\programs\python\python38\python.exe
cachedir: .pytest_cache
metadata: {'Python': '3.8.8', 'Platform': 'Windows-10-10.0.19041-SP0', 'Packages': {'pytest': '6.2.3', 'py': '1.10.0', 'pluggy': '0.13.1'}, 'Plugins': {'html': '3.1.1', 'metadata': '1.11.0', 'rerunfailures': '9.1.1', 'assume': '2.2.0', 'requests-mock': '1.7.0'}, 'JAVA_HOME': 'C:\\Program Files\\Java\\jdk1.8.0_271', 'foo': 'bar'}

rootdir: C:\Users\057776\PycharmProjects\pytest-demo\testfixture
plugins: html-3.1.1, metadata-1.11.0, rerunfailures-9.1.1, assume-2.2.0, requests-mock-1.7.0
collected 3 items                                                                                                                                                   

test_fixture.py::test_one 开始加载module级别夹具
开始加载function级别夹具
PASSED开始退出function级别夹具

test_fixture.py::TestFixture::test_two 开始加载class级别夹具
开始加载function级别夹具
PASSED开始退出function级别夹具

test_fixture.py::TestFixture::test_three PASSED开始退出class级别夹具
开始退出module级别夹具


=================================== 3 passed in 0.08s ===================================

(venv) C:\Users\057776\PycharmProjects\pytest-demo\testfixture>

可知,module级别的夹具在测试文件中所有用例执行完成后销毁。

4、session会话级别,autouse=True

修改conftest.py配置文件如下,新增一个session级别的夹具。这里需要注意参数 autouse=True会话级别的夹具才能生效。

import pytest


@pytest.fixture(scope='function')
def myfixture_function():
    print("开始加载function级别夹具")
    yield 100
    print("开始退出function级别夹具")


@pytest.fixture(scope='class')
def myfixture_class():
    print("开始加载class级别夹具")
    yield 200
    print("开始退出class级别夹具")


@pytest.fixture(scope='module')
def myfixture_module():
    print("开始加载module级别夹具")
    yield 300
    print("开始退出module级别夹具")


@pytest.fixture(scope='session', autouse=True)
def myfixture_session():
    print("开始加载session级别夹具")
    yield 400
    print("开始退出session级别夹具")

在testfixture目录中新建test_sample.py文件如下:

def test_session(myfixture_function):
    assert myfixture_function == 100

进入testfixture目录,运行命令:pytest -vs

(venv) C:\Users\057776\PycharmProjects\pytest-demo\testfixture>pytest -vs
=================================== test session starts ===================================
platform win32 -- Python 3.8.8, pytest-6.2.3, py-1.10.0, pluggy-0.13.1 -- e:\programs\python\python38\python.exe
cachedir: .pytest_cache
metadata: {'Python': '3.8.8', 'Platform': 'Windows-10-10.0.19041-SP0', 'Packages': {'pytest': '6.2.3', 'py': '1.10.0', 'pluggy': '0.13.1'}, 'Plugins': {'html': '3.1.1', 'metadata': '1.11.0', 'rerunfailures': '9.1.1', 'assume': '2.2.0', 'requests-mock': '1.7.0'}, 'JAVA_HOME': 'C:\\Program Files\\Java\\jdk1.8.0_271', 'foo': 'bar'}

rootdir: C:\Users\057776\PycharmProjects\pytest-demo\testfixture
plugins: html-3.1.1, metadata-1.11.0, rerunfailures-9.1.1, assume-2.2.0, requests-mock-1.7.0
collected 4 items                                                                                                                                                   

test_fixture.py::test_one 开始加载session级别夹具
开始加载module级别夹具
开始加载function级别夹具
PASSED开始退出function级别夹具

test_fixture.py::TestFixture::test_two 开始加载class级别夹具
开始加载function级别夹具
PASSED开始退出function级别夹具

test_fixture.py::TestFixture::test_three PASSED开始退出class级别夹具
开始退出module级别夹具

test_sample.py::test_session 开始加载function级别夹具
PASSED开始退出function级别夹具
开始退出session级别夹具


=================================== 4 passed in 0.09s ===================================

(venv) C:\Users\057776\PycharmProjects\pytest-demo\testfixture>

可知,session级别的夹具在pytest运行完所有测试用例后销毁。

四、使用夹具(进阶)

1、夹具参数化

params:一个可选的参数列表,默认值为None,可以用来实现 参数化,参数化的时候这里需要用到一个参数request,使用“request.param”来接受请求参数列表,进而实现参数化。

2、夹具中使用其它夹具

pytest 的最大优势之一是其极其灵活的夹具系统。它允许我们将复杂的测试需求归结为更简单和有组织的功能,我们只需要让每个功能描述它们所依赖的东西。

例如下面的例子,新建test_append.py文件,在夹具order中使用了夹具first_entry。

import pytest


# Arrange
@pytest.fixture
def first_entry():
    return "a"


# Arrange
@pytest.fixture
def order(first_entry):
    return [first_entry]


def test_string(order):
    # Act
    order.append("b")

    # Assert
    assert order == ["a", "b"]

3、多个测试函数重复使用一个夹具

夹具可以重复使用,这是pytest功能强大的另外一个原因。不同的测试函数可以请求相同的夹具,并让 pytest 为每个测试提供来自该夹具的自己的结果,这样夹具就可以提供一致的、可重复的结果给不同的测试函数使用。

例如下面的例子,修改test_append.py文件,2个测试函数test_string,test_int都使用了夹具order。

import pytest


# Arrange
@pytest.fixture
def first_entry():
    return "a"


# Arrange
@pytest.fixture
def order(first_entry):
    return [first_entry]


def test_string(order):
    # Act
    order.append("b")

    # Assert
    assert order == ["a", "b"]


def test_int(order):
    # Act
    order.append(2)

    # Assert
    assert order == ["a", 2]

4、测试函数使用多个夹具

测试函数或者夹具中也可以使用多个夹具,例如下面的例子,修改test_append.py文件如下:

import pytest


# Arrange
@pytest.fixture
def first_entry():
    return "a"


# Arrange
@pytest.fixture
def second_entry():
    return 2


# Arrange
@pytest.fixture
def order(first_entry, second_entry):
    return [first_entry, second_entry]


# Arrange
@pytest.fixture
def expected_list():
    return ["a", 2, 3.0]


def test_string(order, expected_list):
    # Act
    order.append(3.0)

    # Assert
    assert order == expected_list

5、使用request内置夹具读取上下文信息

request是给请求测试函数提供信息的特殊夹具。请求夹具,来自测试函数或者夹具函数。请求对象允许访问请求测试函数上下文,并且在间接参数化夹具的情况下具有可选的“param”属性。

import pytest

data = [1, 2, 3]


@pytest.fixture(params=data)
def myfixture(request):
    return request.param, request.config


def test_one(myfixture):
    print(myfixture)


reference:​​

API Reference — pytest documentation

How to use fixtures — pytest documentation

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐