среда, 11 июня 2014 г.

Часть 2. Фикстуры pytest

Всем привет
В первой части немного познакомились с фреймворком для тестирования py.test, а в этой немного углубимся и посмотрим на дополнительные инструменты py.test.

Что такое test fixtures

В тестировании обычно под этим понимают флоу теста, его контекст.
В py.test предусмотрены следующие типы фикстур: встроенные фикстуры, вспомогательные функции, декораторы


setup/teardown

Часто перед запуском теста нужно провести базовые подготовления. Например, это может быть настройка окружения, запуск тестовых сервисов, подготовка тестовых данных, инициализация соединения с базами и проч. Для выполнения этих операций в py.test входит небольшой набор вспомогательных функций - несколько пар setup/teardown.

setup_module(module)
teardown_module(module)

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

setup_function(function)
teardown_function(function)

Делают тоже самое что и предыдущие функции, с единственным исключением - функции вызываются перед/после каждым выполнением всех тестовых сценариев. 
Рассмотрим небольшой надуманный пример, для понимания механизма

#fixtures.py
def setup_module(module):
    print('\nsetup_module()')


def teardown_module(module):
    print('\nteardown_module()')


def setup_function(function):
    print('\nsetup_function()')


def teardown_function(function):
    print('\nteardown_function()')


def test_first():
    print('\ntest_first()')


def test_second():
    print('\ntest_second()')


class TestMyClass:
    @classmethod 
    def setup_class(cls):
        print ('\nsetup_class()')

    @classmethod 
    def teardown_class(cls):
        print ('teardown_class()')

    def setup_method(self, method):
        print ('\nsetup_method()')

    def teardown_method(self, method):
        print ('\nteardown_method()')

    def test_first(self):
        print('test_first()')

    def test_second(self):
        print('test_second()')



>>>py.test -s -v fixtures.py

============================= test session starts =============================
platform win32 -- Python 2.7.5 -- py-1.4.20 -- pytest-2.5.2 -- C:\Python27\python.exe
collected 4 items

fixtures.py:17: test_first
setup_module()

setup_function()

test_first()
PASSED
teardown_function()

fixtures.py:21: test_second
setup_function()

test_second()
PASSED
teardown_function()

fixtures.py:40: TestMyClass.test_first
setup_class()

setup_method()
test_first()
PASSED
teardown_method()

fixtures.py:43: TestMyClass.test_second
setup_method()
test_second()
PASSED
teardown_method()
teardown_class()

teardown_module()
========================== 4 passed in 0.02 seconds ===========================


Встроенные фикстуры

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

>>>py.test --fixtures

Для ознакомления в данном посте рассмотри одну из встроенных фикстур tempdir
temdir
Очень полезная вещь, предназначена для работы с темповыми данными. Т.е. в темповой директории можно хранить любые временные файлы. Например, это могут быть логи тестируемых программ или какой-то контекcт теста, который может понадобиться для воспроизведения бага.

Использовать крайне просто

#fixtures.py
def test_file(tmpdir):
    f = tmpdir.mkdir('logs').join('test.log')
    print(tmpdir.strpath)
    f.write('bla bla')

    with open(f.strpath, 'r') as fp:
        print(fp.read())

>>>py.test -s -v fixtures.py
============================= test session starts =============================
platform win32 -- Python 2.7.5 -- py-1.4.20 -- pytest-2.5.2 -- C:\Python27\python.exe
collected 1 items

fixtures.py:161: test_file \temp\pytest-76\test_file0
bla bla
PASSED

========================== 1 passed in 0.03 seconds ===========================

Полезные фикстуры

Для итерации по входным данным можно использовать полезный декоратор py.test.mark.parametrize

#fixtures.py
@py.test.mark.parametrize('num', [i for i in range(2)])
def test(num):
    assert num % 2


py.test -s -v fixtures.py
============================= test session starts =============================
platform win32 -- Python 2.7.5 -- py-1.4.20 -- pytest-2.5.2 -- C:\Python27\python.exe
collected 2 items

fixtures.py:182: test[0] FAILED
fixtures.py:182: test[1] PASSED

================================== FAILURES ===================================
___________________________________ test[0] ___________________________________

num = 0

    @py.test.mark.parametrize('num', [i for i in range(2)])
    def test(num):
>       assert num % 2
E       assert (0 % 2)

fixtures.py:184: AssertionError
===================== 1 failed, 1 passed in 0.02 seconds ======================

Кастомизация

О кастомных фикстурах более подробно в одном из следующих постов, поэтому здесь совсем небольшой пример.

Для определения собственных инструментов в py.test предусмотрены 

#fixtures.py
@pytest.fixture(scope="function")
def before(request):
    print('\nbefore()')

    def after():
        print("\nafter()")
    request.addfinalizer(after)


def test_func(before):
    print("\ntest_func()")



>>>py.test -s -v fixtures.py
============================= test session starts =============================
platform win32 -- Python 2.7.5 -- py-1.4.20 -- pytest-2.5.2 -- C:\Python27\python.exe
collected 1 items

fixtures.py:14: test_func
before()

test_func()
PASSED
after()
========================== 1 passed in 0.01 seconds ===========================

Вывод

1. Прежде всего это создано для удобства (как собственно и весь py.test)
2. Сокращает время разработки теста
3. делает тест прозрачнее
4. Уменьшает количество копипаста


Ссылки

http://pytest.org/latest/
http://en.wikipedia.org/wiki/Test_fixture
http://pythontesting.net/

2 комментария:

  1. Спасибо большое, реально фиг что найдешь по pytest ( Хотя так странно

    ОтветитьУдалить
    Ответы
    1. Денис, есть годная дока и куча вопросов на стековерфлоу. На русском к сожалению информации практически нет.

      Удалить