Нескучное тестирование с pytest

34
Нескучное тестирование с pytest Роман Иманкулов / @rdotpy / 27 июня 2014

description

Написание юнит-тестов большинству представляется занятием скучным и до некоторой степени бесполезным. Мое мнение — это всё оттого, что сама "классическая" схема юнит-тестов подразумевает непродуктивное написание унылого линейного кода. В докладе я расскажу о том, как с помощью pytest начать писать тесты, которые приятно читать и поддерживать, почему setUp и tearDown — это прошлый век, как с помощью правильной организации fixtures ускорить исполнение тестов, а также какие ещё уловки могут помочь вам в вашей нелегкой борьбе с рутиной.

Transcript of Нескучное тестирование с pytest

Page 1: Нескучное тестирование с pytest

Нескучное тестированиес pytest

Роман Иманкулов / @rdotpy / 27 июня 2014

Page 2: Нескучное тестирование с pytest

Почему программисты не любят писать тесты?

Page 3: Нескучное тестирование с pytest

Тестирование в Python — это религия

• Врождённая греховность• Очищение через страдание• Мистический опыт

Page 4: Нескучное тестирование с pytest

Врождённая греховность

Врожденные пороки — нестрогая типизация и duck typing

• Как следствие — природная склонность программиста на Python к совершению маленьких и глупых ошибок

Page 5: Нескучное тестирование с pytest

Очищение через страдание

Boilerplate Code

    class TestSequenceFunctions(unittest.TestCase):        def setUp(self):            ...        def tearDown(self):            ...        def testFoo(self):            ...

Page 6: Нескучное тестирование с pytest

Очищение через страдание

Многословные ассерты

    

    self.assertEqual(foo, 1,                      'foo is not equal to one')

Page 7: Нескучное тестирование с pytest

Мистический опыт

Django testing setups & teardowns

Page 8: Нескучное тестирование с pytest

Есть ли альтернатива?

Page 9: Нескучное тестирование с pytest

pytest

Page 10: Нескучное тестирование с pytest
Page 11: Нескучное тестирование с pytest
Page 12: Нескучное тестирование с pytest

pytest — это не еще одинxUnit фреймворк!

Page 13: Нескучное тестирование с pytest

pytest fixtures

То, что отличает pytest от других фреймворков

Page 14: Нескучное тестирование с pytest

pytest fixtures

Наивный подход. Как это бы сделал я сам

file: fixtures.py

    def get_user():        return User(name='Roman', age=30, ...)        

file: test_user.py

    def test_user():        user = get_user()        assert user.name == 'Roman'

Page 15: Нескучное тестирование с pytest

pytest fixtures

Подход pytest

file: conftest.py

    @pytest.fixture    def user():        return User(name='Roman', age=30, ...)        

file: test_user.py

    def test_user(user):        assert user.name == 'Roman'

Page 16: Нескучное тестирование с pytest

Зависимости между fixtures

Page 17: Нескучное тестирование с pytest

@pytest.fixturedef user():    return User(name='Roman', age=30, ...)       

@pytest.fixturedef task(user):    return Task(user=user, name='...')

def test_task(task):    assert task.user.name == 'Roman'

Page 18: Нескучное тестирование с pytest

Fixture dependencies. Patching object

@pytest.fixturedef premium(user)    user.set_premium()

def test_premium(user, premium):    assert user.is_premum()

Page 19: Нескучное тестирование с pytest

yield_fixturesetup и teardownв одном флаконе

Page 20: Нескучное тестирование с pytest

@pytest.yield_fixturedef user():    obj = User(name='Roman', age=30, ...)           yield obj    obj.delete()

Page 21: Нескучное тестирование с pytest

Fixture scopes

• function scope• module scope• session scope

Page 22: Нескучное тестирование с pytest

Session fixture. Локальный кеш

@pytest.yield_fixture(scope='session', autouse=True)def local_cache():    old_settings = settings.CACHES    settings.CACHES = {'default': {…}}    yield    settings.CACHES = old_settings

Page 23: Нескучное тестирование с pytest

Function fixture. Database transaction rollback

@pytest.yield_fixturedef tx():    db().start_transaction()    yield    db().rollback()

def test_user(user, tx, project, task):    # project & task will be removed automatically

Page 24: Нескучное тестирование с pytest

Session fixture. Чистый redis

@pytest.yield_fixture(scope='session')def redis_server():    proc = subp.Popen(['redis­server', '­­port', 7777], ... )    yield proc    proc.terminate()

@pytest.fixturedef rc(redis_server):    client = redis.StrictRedis('redis://127.0.0.1:7777')    client.flushall()    return client

Page 25: Нескучное тестирование с pytest

fixtures parametrization

Page 26: Нескучное тестирование с pytest

Функция возвращает функцию

@pytest.fixturedef set_lang(user):    def func(lang_code):            user.set_lang(lang_code)    return func

def test_languages(user, set_lang):    set_lang('ru')    ...

Page 27: Нескучное тестирование с pytest

Странные вещи

Page 28: Нескучное тестирование с pytest

Fixtures в отдельном потоке http://bit.ly/test_pool

@pytest.fixture(scope='session')def item_gen():    gen = Generator(lambda: .)    gen.start()    return gen

@pytest.yield_fixture

def item(item_gen, item_rel):

    item = item_gen.get()

    yield item

    item_rel.put(item)

@pytest.fixture(scope='session')

def item_rel():

    rel = Releaser(lambda o: ...)

    rel.start()

    return rel

Page 29: Нескучное тестирование с pytest

Как ещё использовать fixtures

• warnings: turn MySQL warnings to errors• mock: подготовка mockup объектов• freezegun: управление временем• selenium: запуск веб-драйвера

Page 30: Нескучное тестирование с pytest

О чём я ещё не рассказал

def pytest_addoption(parser):

    parser.addoption("­­clean­mysql",

  action="store_true", default=False)

@pytest.fixture(scope='session', autouse=True)

def clean_mysql(request):

    if not request.config.getoption('­­clean­mysql'):           return

     # clean MySQL tables heres

Page 31: Нескучное тестирование с pytest

О чём я ещё не рассказал

@pytest.mark.parametrize("input,expected", [

    ("3+5", 8),

    ("2+4", 6),

])

def test_eval(input, expected):

    assert eval(input) == expected

Page 32: Нескучное тестирование с pytest

О чём я ещё не рассказал

• pytest-django: интеграция с Django

• pytest-xdist: параллельные и распределенные тесты

Page 33: Нескучное тестирование с pytest

О чём я ещё не рассказал

• tox: выполнение тестов для разных python

• detox: то же самое, только параллельно

[tox]

envlist = py26,py27

[testenv]

deps=pytest  

commands=py.test

Page 34: Нескучное тестирование с pytest

Спасибо! Вопросы?

Роман Иманкулов / @rdotpy / http://imankulov.name