Разделы портала

Онлайн-тренинги

.
Прелести Pytest: фикстуры
15.06.2020 00:00

Автор: Джош Грант (Josh Grant)
Оригинал статьи
Перевод: Ольга Алифанова

Продолжая рассказывать о чудесных возможностях Pytest, я хочу немного поговорить о старой, но прекрасной фиче – о фикстурах.

Фикстуры – интересная и часто смущающая новичков тема в Pytest. Вначале они кажутся контринтуитивными и попросту неправильными, но как только вы поймете, как это работает, фикстуры станут неотъемлемой частью хорошего кода Pytest.


Начнем с начала. Что такое фикстура? Это функция, несущая логику, применяемую в определенном контексте. Предположим для примера, что мы хотим протестировать библиотеку генератора случайных чисел по имени Rando. Мы можем использовать фикстуры для тестирования объект-экземпляра:

@pytest.fixture
def rando():
return Rando(0,1)
 
def test_upper_range(rando):
val = rando.value()
 
assert val < 1
 
def test_lower_range(rando):
val = rando.value()
 
assert val > 0

Эта фикстура возвращает экземпляр Rando по имени rando, который можно передавать в функции. Каждая функция получает свой собственный независимый экземпляр rando, который можно использовать в логическом теле функции. Это кажется довольно простым синтаксически, но тут происходит кое-что интересное.

Во-первых, мы не используем в коде объектно-ориентированное программирование. Две вышеуказанных функции не обязаны быть членами класса. Фикстура может использоваться этими функциями, и не должна сохранять состояние между тестами.

Приведу пример объектно-ориентированного псевдокода:

class TestRando(){
 
private Rando rando;
 
private setUp() { this.rando = new Rando(0,1); }
 
@Test
private testUpperRange() {
double val = rando.value()
 
assert val < 1
}
 
@Test
private testLowerRange() {
double val = rando.value()
 
assert val > 0
}
}

Это более традиционный подход, который тоже сработает с классами в Python. Удивительный красивый момент тут в том, что ООП не требуется для работы с Pytest. И, как оказывается, для работы с Pytest ООП лучше вовсе не использовать.

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

Предположим, что в вышеприведенном примере генератора случайных чисел можно создать и передать RandoSeed в экземпляр Rando. Если мы хотим одновременно проверить и использовать его, мы создадим такие фикстуры:

@pytest.fixture
def seed():
return RandoSeed(0)
 
@pytest.fixture
def seeded(seed):
return Rando(0,1, seed=seed)
 
@pytest.fixture
def unseeded():
return Rando(0,1)
 
def test_seeded_value(seeded):
actual = rando.val()
expected = SEEDED_VAL
 
assert actual == expected
 
def test_upper_range(unseeded):
val = rando.value()
 
assert val < 1
 
def test_lower_range(unseeded):
val = rando.value()
 
assert val > 0

(Не волнуйтесь, если вам не знакома генерация случайных чисел. Суть тут в подходе к тесту, а не в конкретных тестируемых свойствах. Генерация случайных чисел – сложная штука).

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

Эти примеры – это только начало использования фикстур в Python, но они дают общее представление о том, как работают фикстуры и какие проблемы они решают. Жизнь с фикстурами Pytest хороша!

Обсудить в форуме