Python Languagepy.test

Настройка py.test

py.test - одна из нескольких сторонних библиотек тестирования , доступных для Python. Он может быть установлен с помощью pip с

pip install pytest

Код для тестирования

Скажем, мы тестируем функцию добавления в projectroot/module/code.py :

# projectroot/module/code.py
def add(a, b):
    return a + b

Тестирование

Мы создаем тестовый файл в projectroot/tests/test_code.py . Файл должен начинаться с test_ для распознавания в качестве файла тестирования.

# projectroot/tests/test_code.py
from module import code


def test_add():
    assert code.add(1, 2) == 3

Выполнение теста

Из projectroot мы просто запускаем py.test :

# ensure we have the modules
$ touch tests/__init__.py
$ touch module/__init__.py
$ py.test
================================================== test session starts ===================================================
platform darwin -- Python 2.7.10, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
rootdir: /projectroot, inifile:
collected 1 items

tests/test_code.py .

================================================ 1 passed in 0.01 seconds ================================================

Неудачные тесты

Пробный тест даст полезную информацию о том, что пошло не так:

# projectroot/tests/test_code.py
from module import code


def test_add__failing():
    assert code.add(10, 11) == 33

Результаты:

$ py.test
================================================== test session starts ===================================================
platform darwin -- Python 2.7.10, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
rootdir: /projectroot, inifile:
collected 1 items

tests/test_code.py F

======================================================== FAILURES ========================================================
___________________________________________________ test_add__failing ____________________________________________________

    def test_add__failing():
>       assert code.add(10, 11) == 33
E       assert 21 == 33
E        +  where 21 = <function add at 0x105d4d6e0>(10, 11)
E        +    where <function add at 0x105d4d6e0> = code.add

tests/test_code.py:5: AssertionError
================================================ 1 failed in 0.01 seconds ================================================

Введение в тестовые светильники

Более сложные тесты иногда должны иметь настройки, прежде чем запускать код, который вы хотите проверить. Это можно сделать в самой тестовой функции, но тогда вы оказываете большие функции тестирования, делая так много, что трудно сказать, где остановится установка, и начинается тест. Вы также можете получить много дублирующего кода установки между различными функциями тестирования.

Наш файл кода:

# projectroot/module/stuff.py
class Stuff(object):
    def prep(self):
        self.foo = 1
        self.bar = 2

Наш тестовый файл:

# projectroot/tests/test_stuff.py
import pytest
from module import stuff


def test_foo_updates():
    my_stuff = stuff.Stuff()
    my_stuff.prep()
    assert 1 == my_stuff.foo
    my_stuff.foo = 30000
    assert my_stuff.foo == 30000


def test_bar_updates():
    my_stuff = stuff.Stuff()
    my_stuff.prep()
    assert 2 == my_stuff.bar
    my_stuff.bar = 42
    assert 42 == my_stuff.bar

Это довольно простые примеры, но если нашему объекту Stuff понадобилось гораздо больше настроек, оно получило бы громоздкий характер. Мы видим, что между нашими тестовыми примерами существует дублированный код, поэтому давайте сначала переформулируем его в отдельную функцию.

# projectroot/tests/test_stuff.py
import pytest
from module import stuff


def get_prepped_stuff():
    my_stuff = stuff.Stuff()
    my_stuff.prep()
    return my_stuff


def test_foo_updates():
    my_stuff = get_prepped_stuff()
    assert 1 == my_stuff.foo
    my_stuff.foo = 30000
    assert my_stuff.foo == 30000


def test_bar_updates():
    my_stuff = get_prepped_stuff()
    assert 2 == my_stuff.bar
    my_stuff.bar = 42
    assert 42 == my_stuff.bar

Это выглядит лучше, но у нас все еще есть my_stuff = get_prepped_stuff() загромождающий наши тестовые функции.

py.test приспособления на помощь!

Светильники - это гораздо более мощные и гибкие версии функций тестовой настройки. Они могут сделать намного больше, чем мы используем здесь, но мы будем делать это шаг за шагом.

Сначала мы меняем get_prepped_stuff на приспособление, называемое prepped_stuff . Вы хотите называть свои светильники именами, а не глаголами, из-за того, как приборы впоследствии будут использоваться в самих тестовых функциях. Параметр @pytest.fixture указывает, что эту конкретную функцию следует обрабатывать как привязку, а не как обычную функцию.

@pytest.fixture
def prepped_stuff():
    my_stuff = stuff.Stuff()
    my_stuff.prep()
    return my_stuff

Теперь мы должны обновить тестовые функции, чтобы они использовали прибор. Это делается путем добавления параметра к определению, которое точно соответствует имени прибора. Когда py.test выполняется, он запускает прибор перед запуском теста, а затем передает возвращаемое значение прибора в тестовую функцию через этот параметр. (Обратите внимание, что светильники не должны возвращать значение, они могут делать другие вещи настройки, например, вызывать внешний ресурс, упорядочивать вещи в файловой системе, помещать значения в базу данных, независимо от того, какие тесты необходимы для настройки)

def test_foo_updates(prepped_stuff):
    my_stuff = prepped_stuff
    assert 1 == my_stuff.foo
    my_stuff.foo = 30000
    assert my_stuff.foo == 30000


def test_bar_updates(prepped_stuff):
    my_stuff = prepped_stuff
    assert 2 == my_stuff.bar
    my_stuff.bar = 42
    assert 42 == my_stuff.bar

Теперь вы можете понять, почему мы назвали его существительным. но my_stuff = prepped_stuff практически бесполезна, поэтому давайте просто использовать prepped_stuff прямо.

def test_foo_updates(prepped_stuff):
    assert 1 == prepped_stuff.foo
    prepped_stuff.foo = 30000
    assert prepped_stuff.foo == 30000


def test_bar_updates(prepped_stuff):
    assert 2 == prepped_stuff.bar
    prepped_stuff.bar = 42
    assert 42 == prepped_stuff.bar

Теперь мы используем светильники! Мы можем пойти дальше, изменив область действия прибора (так что он запускается только один раз на тестовый модуль или сеанс выполнения тестового набора, а не один раз на каждую тестовую функцию), создавая светильники, которые используют другие приборы, параметризуя прибор (чтобы светильник и все тесты, использующие этот прибор, выполняются несколько раз, один раз для каждого параметра, присвоенного прибору), приборы, которые считывают значения из модуля, который их вызывает ..., как упоминалось ранее, светильники обладают гораздо большей мощностью и гибкостью, чем обычная функция настройки.

Очистка после проведения тестов.

Скажем, наш код вырос, и нашему объекту Stuff теперь нужна специальная очистка.

# projectroot/module/stuff.py
class Stuff(object):
def prep(self):
    self.foo = 1
    self.bar = 2

def finish(self):
    self.foo = 0
    self.bar = 0

Мы могли бы добавить код для вызова очистки в нижней части каждой тестовой функции, но приспособления обеспечивают лучший способ сделать это. Если вы добавите функцию в прибор и зарегистрируете его в качестве финализатора , код в функции финализатора будет вызван после теста с использованием приспособления. Если объем устройства больше одной функции (например, модуля или сеанса), финализатор будет выполнен после завершения всех тестов в области видимости, поэтому после завершения работы модуля или в конце всего сеанса тестирования ,

@pytest.fixture
def prepped_stuff(request):  # we need to pass in the request to use finalizers
    my_stuff = stuff.Stuff()
    my_stuff.prep()
    def fin():  # finalizer function
        # do all the cleanup here
        my_stuff.finish()
    request.addfinalizer(fin)  # register fin() as a finalizer
    # you can do more setup here if you really want to
    return my_stuff

Использование функции финализатора внутри функции может быть немного сложно понять на первый взгляд, особенно когда у вас есть более сложные приборы. Вместо этого вы можете использовать приспособление с урожаем, чтобы сделать то же самое с более понятным для пользователя потоком выполнения. Единственное реальное отличие заключается в том, что вместо использования return мы используем yield в той части прибора, где выполняется настройка, и управление должно перейти к тестовой функции, а затем добавить весь код очистки после yield . Мы также украшаем его как yield_fixture так что py.test знает, как с ним справиться.

@pytest.yield_fixture
def prepped_stuff():  # it doesn't need request now!
    # do setup
    my_stuff = stuff.Stuff()
    my_stuff.prep()
    # setup is done, pass control to the test functions
    yield my_stuff
    # do cleanup 
    my_stuff.finish()

И это завершает Intro для тестирования светильников!

Для получения дополнительной информации см. Официальную документацию по документации на py.test и официальную документацию по инвентаризации