pytest总结之pytest的 Fixture

pytest总结之pytest的 Fixture

    • 3.pytest Fixture
      • 3.1通过conftest.py 共享fixture
      • 3.2使用fixture执行配置和销毁逻辑
      • 3.3使用--setup-show回溯fixture的执行过程
      • 3.4使用fixture传递数据
      • 3.5使用多个fixture
      • 3.6指定fixture作用范围
        • scope='function' 函数级别
        • scope='class' 类级别
        • scope='module' 模块级别
        • scope='session' 会话级别
      • 3.7使用userfixtures指定fixture
      • 3.8为fixture添加autouse选项
      • 3.9为fixture重命名
      • 3.10Fixture的参数化

3.pytest Fixture

fixture是在测试函数运行之后由pytest执行的外壳函数。

以下是一个返回值的简单fixture

@pytest.fixture()定义一个fixture 用的时候在函数里直接传入名字

"""Demonstrate simple fixtures."""

import pytest


@pytest.fixture()
def some_data():
    """Return answer to ultimate question."""
    return 42


def test_some_data(some_data):
    """Use fixture return value in a test."""
    assert some_data == 42


@pytest.fixture()
def some_other_data():
    """Raise an exception from fixture."""
    x = 43
    assert x == 42
    return x


def test_other_data(some_other_data):
    """Try to use failing fixture."""
    assert some_data == 42


@pytest.fixture()
def a_tuple():
    """Return something more interesting."""
    return (1, 'foo', None, {'bar': 23})


def test_a_tuple(a_tuple):
    """Demo the a_tuple fixture."""
    assert a_tuple[3]['bar'] == 32

3.1通过conftest.py 共享fixture

fixture可以放到单独的文件里,如果你希望多个文件共享fixture,可以在某个公共的路径创建一个conftest.py,将fixture放在其中。

"""Define some fixtures to use in the project."""

import pytest
import tasks
from tasks import Task


@pytest.fixture()
def tasks_db(tmpdir):
    """Connect to db before tests, disconnect after."""
    # Setup : start db
    tasks.start_tasks_db(str(tmpdir), 'tiny')

    yield  # this is where the testing happens

    # Teardown : stop db
    tasks.stop_tasks_db()



# Reminder of Task constructor interface
# Task(summary=None, owner=None, done=False, id=None)
# summary is required
# owner and done are optional
# id is set by database

@pytest.fixture()
def tasks_just_a_few():
    """All summaries and owners are unique."""
    return (
        Task('Write some code', 'Brian', True),
        Task("Code review Brian's code", 'Katie', False),
        Task('Fix what Brian did', 'Michelle', False))


@pytest.fixture()
def tasks_mult_per_owner():
    """Several owners with several tasks each."""
    return (
        Task('Make a cookie', 'Raphael'),
        Task('Use an emoji', 'Raphael'),
        Task('Move to Berlin', 'Raphael'),

        Task('Create', 'Michelle'),
        Task('Inspire', 'Michelle'),
        Task('Encourage', 'Michelle'),

        Task('Do a handstand', 'Daniel'),
        Task('Write some books', 'Daniel'),
        Task('Eat ice cream', 'Daniel'))


@pytest.fixture()
def db_with_3_tasks(tasks_db, tasks_just_a_few):
    """Connected db with 3 tasks, all unique."""
    for t in tasks_just_a_few:
        tasks.add(t)


@pytest.fixture()
def db_with_multi_per_owner(tasks_db, tasks_mult_per_owner):
    """Connected db with 9 tasks, 3 owners, all with 3 tasks."""
    for t in tasks_mult_per_owner:
        tasks.add(t)

可以按着目录级别放置conftest.py 在次路径下的所有案例讲执行。

3.2使用fixture执行配置和销毁逻辑

fixture函数会在测试函数之前执行,但是如果fixture函数包含一个yield,那么系统将会在yield处停止,转而执行yield后面的代码。因此,可以将yield之前的代码视为配置(setup)过程,将yield之后的代码视为处理(teardown)过程。无论测试过程发生了什么,yield之后的代码都会被执行。

@pytest.fixture()
def tasks_db(tmpdir):
    """Connect to db before tests, disconnect after."""
    # Setup : start db
    tasks.start_tasks_db(str(tmpdir), 'tiny')

    yield  # this is where the testing happens

    # Teardown : stop db
    tasks.stop_tasks_db()


def test_add_returns_valid_id(tasks_db):
    """tasks.add() should return an integer."""
    # GIVEN an initialized tasks db
    # WHEN a new task is added
    # THEN returned task_id is of type int
    new_task = Task('do something')
    task_id = tasks.add(new_task)
    assert isinstance(task_id, int)

3.3使用–setup-show回溯fixture的执行过程

编写fixture时希望看到测试过程中执行的是什么,及执行的先后顺序,pytet提供了 --setup-show可以实现这个功能。

pytest --setup-show test_add.py -k valid_id

collected 3 items / 2 deselected / 1 selected                                                                                                                              

test_add.py 
SETUP    S tmp_path_factory
        SETUP    F tmp_path (fixtures used: tmp_path_factory)
        SETUP    F tmpdir (fixtures used: tmp_path)
        SETUP    F tasks_db (fixtures used: tmpdir)
        func/test_add.py::test_add_returns_valid_id (fixtures used: request, tasks_db, tmp_path, tmp_path_factory, tmpdir).
        TEARDOWN F tasks_db
        TEARDOWN F tmpdir
        TEARDOWN F tmp_path
TEARDOWN S tmp_path_factory

===================================================================== 1 passed, 2 deselected in 0.08s ======================================================================


Fixture 前面的S和F代表作用范围,F代表函数级别的 S代表会话级别的。3.6中会有总结。

3.4使用fixture传递数据

fixture非常适合存放测试数据,并且它可以返回任何数据。

fixture失败案例会返回ERROR

fixture成功案例失败会返回Faile 这点pytest做的很好。

# 这是fixture没问题 案例断言会失败 案例状态执行后会为faile
@pytest.fixture()
def a_tuple():
    """Return something more interesting."""
    return (1, 'foo', None, {'bar': 23})


def test_a_tuple(a_tuple):
    """Demo the a_tuple fixture."""
    assert a_tuple[3]['bar'] == 32
# 这个fixture本身有断言,在fixture中就失败了 案例是ERROR状态 
@pytest.fixture()
def some_other_data():
    """Raise an exception from fixture."""
    x = 43
    assert x == 42
    return x


def test_other_data(some_other_data):
    """Try to use failing fixture."""
    assert some_data == 42

collected 3 items / 1 deselected / 2 selected

test_fixtures.py EF [100%]

================================================================================== ERRORS ==================================================================================
____________________________________________________________________ ERROR at setup of test_other_data _____________________________________________________________________

@pytest.fixture()
def some_other_data():
    """Raise an exception from fixture."""
    x = 43
  assert x == 42
  E       assert 43 == 42

test_fixtures.py:21: AssertionError
================================================================================= FAILURES =================================================================================
_______________________________________________________________________________ test_a_tuple _______________________________________________________________________________

a_tuple = (1, ‘foo’, None, {‘bar’: 23})

def test_a_tuple(a_tuple):
    """Demo the a_tuple fixture."""
  assert a_tuple[3]['bar'] == 32
  E       assert 23 == 32

test_fixtures.py:38: AssertionError

我们可以用fixture返回多个数据

@pytest.fixture()
def tasks_db(tmpdir):
    """Connect to db before tests, disconnect after."""
    # Setup : start db
    tasks.start_tasks_db(str(tmpdir), 'tiny')

    yield  # this is where the testing happens

    # Teardown : stop db
    tasks.stop_tasks_db()

@pytest.fixture()
def tasks_just_a_few():
    """All summaries and owners are unique."""
    return (
        Task('Write some code', 'Brian', True),
        Task("Code review Brian's code", 'Katie', False),
        Task('Fix what Brian did', 'Michelle', False))


@pytest.fixture()
def tasks_mult_per_owner():
    """Several owners with several tasks each."""
    return (
        Task('Make a cookie', 'Raphael'),
        Task('Use an emoji', 'Raphael'),
        Task('Move to Berlin', 'Raphael'),

        Task('Create', 'Michelle'),
        Task('Inspire', 'Michelle'),
        Task('Encourage', 'Michelle'),

        Task('Do a handstand', 'Daniel'),
        Task('Write some books', 'Daniel'),
        Task('Eat ice cream', 'Daniel'))
@pytest.fixture()
def db_with_3_tasks(tasks_db, tasks_just_a_few):
    """Connected db with 3 tasks, all unique."""
    for t in tasks_just_a_few:
        tasks.add(t)


@pytest.fixture()
def db_with_multi_per_owner(tasks_db, tasks_mult_per_owner):
    """Connected db with 9 tasks, 3 owners, all with 3 tasks."""
    for t in tasks_mult_per_owner:
        tasks.add(t)

3.5使用多个fixture

使用fixture的优势在:用户在编写测试函数时可以只考虑核心测试逻辑,而不是只考虑测试前的准备工作。

要习惯在注释中写 given 、 when、then,并且总是在fixture中尽量多的写given。这样做两个原因:

1.可读性高

2.fixture中的异常不会被报告为fail 而是error。测试失败不要原因是数据引起的,数据错误应该是发生了错误,而不是案例失败。这很重要。

def test_add_returns_valid_id(tasks_db):
    """tasks.add() should return an integer."""
    # GIVEN an initialized tasks db
    # WHEN a new task is added
    # THEN returned task_id is of type int
    new_task = Task('do something')
    task_id = tasks.add(new_task)
    assert isinstance(task_id, int)

3.6指定fixture作用范围

fixture有一个scope的参数指定范围。

scope=‘function’ 函数级别

scope=‘class’ 类级别

scope=‘module’ 模块级别

scope=‘session’ 会话级别

下面举个例子:

"""Demo fixture scope."""

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."""

使用pytest --setup-show 命令可以看到具体调用

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 =======================================

不难看出作用范围是由fixture自身决定的,但还是要强调scope参数是在定义fixture时定义的,而不是在调用时,因此使用fixture的测试函数是无法改变fixture的作用范围的。

注意fixture只能使用同级别的fixture,或者比自己高级的fixture;比如 function函数级别的fixture可以使用同级别的fixture,亦可以使用class类级别、module模块级别、session会话级别;但是不可以反过来。

3.7使用userfixtures指定fixture

就是@pytest.mark.usefixtures(‘xxx’)的使用 很简单其余的没啥用其实,在类测试中用的话非常合适。

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

@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."""

3.8为fixture添加autouse选项

为常用的fixture添加@autouse

"""Demonstrate autouse fixtures."""

import pytest
import time


@pytest.fixture(autouse=True, scope='session')
def footer_session_scope():
    """Report the time at the end of a session."""
    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():
    """Report test durations after each function."""
    start = time.time()
    yield
    stop = time.time()
    delta = stop - start
    print('\ntest duration : {:0.3} seconds'.format(delta))


def test_1():
    """Simulate long-ish running test."""
    time.sleep(1)


def test_2():
    """Simulate slightly longer test."""
    time.sleep(1.23)
#运行测试 pytest -v -s test_autouse.py
#结果 都自动用到了两个测试用例。

collected 2 items                                                                                                                                                          

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

test_autouse.py::test_2 PASSED
test duration : 1.24 seconds
--
finished : 04 Apr 02:12:04
-----------------

=================================== 2 passed in 2.27s =====================================

3.9为fixture重命名

fixture的名字展视通常会与他的函数名保持一致,但是pytest也运行重名名,name参数对fixture重命名。

"""Demonstrate fixture renaming."""

import pytest


@pytest.fixture(name='lue')
def ultimate_answer_to_life_the_universe_and_everything():
    """Return ultimate answer."""
    return 42


def test_everything(lue):
    """Use the shorter name."""
    assert lue == 42

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.02s ========================================



3.10Fixture的参数化

数据驱动fixture 可以让fixture运行多次 ,很强大的功能。

"""Test the tasks.add() API function."""

import pytest
import tasks
from tasks import Task

tasks_to_try = (Task('sleep', done=True),
                Task('wake', 'brian'),
                Task('breathe', 'BRIAN', True),
                Task('exercise', 'BrIaN', False))

task_ids = ['Task({},{},{})'.format(t.summary, t.owner, t.done)
            for t in tasks_to_try]


def equivalent(t1, t2):
    """Check two tasks for equivalence."""
    return ((t1.summary == t2.summary) and
            (t1.owner == t2.owner) and
            (t1.done == t2.done))


@pytest.fixture(params=tasks_to_try)
def a_task(request):
    """Using no ids."""
    return request.param


def test_add_a(tasks_db, a_task):
    """Using a_task fixture (no ids)."""
    task_id = tasks.add(a_task)
    t_from_db = tasks.get(task_id)
    assert equivalent(t_from_db, a_task)


@pytest.fixture(params=tasks_to_try, ids=task_ids)
def b_task(request):
    """Using a list of ids."""
    return request.param


def test_add_b(tasks_db, b_task):
    """Using b_task fixture, with ids."""
    task_id = tasks.add(b_task)
    t_from_db = tasks.get(task_id)
    assert equivalent(t_from_db, b_task)


def id_func(fixture_value):
    """A function for generating ids."""
    t = fixture_value
    return 'Task({},{},{})'.format(t.summary, t.owner, t.done)


@pytest.fixture(params=tasks_to_try, ids=id_func)
def c_task(request):
    """Using a function (id_func) to generate ids."""
    return request.param


def test_add_c(tasks_db, c_task):
    """Use fixture with generated ids."""
    task_id = tasks.add(c_task)
    t_from_db = tasks.get(task_id)
    assert equivalent(t_from_db, c_task)

你可能感兴趣的:(pytest,pytest,python,开发语言)