В данной статье представлены примеры использования фикстур (fixture) в pytest.
Фикстуры в pytest представляют собой функции, которые можно использовать при автоматизации тестирования для решения следующих задач:
выполнение действий до проведения тестирования (напри��ер, настройка тестового окружения, создание тестовых данных, различные сетевые подключения, открытие файла и пр.);
выполнение действий после проведения тестирования (например, удаление временных данных, формирование отчета, отключение сетевых подключений, закрытие файлов и пр.);
вынос типовых действий в отдельные компоненты, что позволяет избежать дублирования кода и улучить его поддержку и читаемость;
корректно выполнять необходимые завершающие действия (финализация), даже в случаи ошибок в тестовых сценариях или при их прерывании.
Дисклеймер: В статье продемонстрированы синтетические примеры, наполненные методом print(), это сделано только для демонстрации последовательности выполнения шагов в stdout. При создании реальных тестовых кейсов рекомендуется использовать лучшие практики и паттерны программирования и проектирования, а также применяемые стандарты при разработке программного кода.
Оглавление
1. Введение
Для понимания как работают фикстуры в pytest необходимо:
умение работать с python;
умение работать с фреймворком для автоматизированного тестирования pytest;
знать, что в python всё является объектами;
принцип работы декоратора (@) для классов, функций и методов.
Фикстуры могут располагаться как внутри тестового файла, так и в отдельном файле (conftest.py). Для назначения фикстур используется декоратор "@pytest.fixture".
2. Обертки для тестовых объектов через фикстуры
Тестовые объекты можно обернуть в фикстуры, которые будут выполнять действия до и после тестового объекта.

import pytest @pytest.fixture(scope='module', autouse=True) def f_wrapper_module(): '''fixture: Обертка для тестового модуля''' print('>', 'fixture: Запуск обертки для тестовго модуля') yield print('>', 'fixture: Завершение обертки для тестовго модуля') @pytest.fixture(scope='class', autouse=True) def f_wrapper_class(): '''fixture: Обертка для тестового класса''' print('>>', 'fixture: Запуск обертки для тестовго класса') yield print('>>', 'fixture: Завершение обертки для тестовго класса') @pytest.fixture(scope='function', autouse=True) def f_wrapper_function(): '''fixture: Обертка для тестовой сессии''' print('>>>', 'fixture: Запуск обертки для тестовой функции') yield print('>>>', 'fixture: Завершение обертки для тестовой функции')
class TestSuite1(): '''Первый testsuite''' def test_1(self): print('TestSuite1: Запуск test1') assert True def test_2(self): print('TestSuite1: Запуск test2') assert True class TestSuite2(): '''Второй testsuite''' def test_1(self): print('TestSuite2: Запуск test1') assert True def test_2(self): print('TestSuite2: Запуск test2') assert True
Параметры в фикстуре:
"scope" - задает область работы fixture и может принимать следующие значения [ 'session' | 'package' | 'module' | 'class' | 'function' ]
"autouse" - автоматически включать данную fixture в область работы, без явного указания
Результат выполнения теста:
$: pytest -vs example.py example.py::TestSuite1::test_1 > fixture: Запуск обертки для тестовго модуля >> fixture: Запуск обертки для тестовго класса >>> fixture: Запуск обертки для тестовой функции TestSuite1: Запуск test1 PASSED >>> fixture: Завершение обертки для тестовой функции example.py::TestSuite1::test_2 >>> fixture: Запуск обертки для тестовой функции TestSuite1: Запуск test2 PASSED >>> fixture: Завершение обертки для тестовой функции >> fixture: Завершение обертки для тестовго класса example.py::TestSuite2::test_1 >> fixture: Запуск обертки для тестовго класса >>> fixture: Запуск обертки для тестовой функции TestSuite2: Запуск test1 PASSED >>> fixture: Завершение обертки для тестовой функции example.py::TestSuite2::test_2 >>> fixture: Запуск обертки для тестовой функции TestSuite2: Запуск test2 PASSED >>> fixture: Завершение обертки для тестовой функции >> fixture: Завершение обертки для тестовго класса > fixture: Завершение обертки для тестовго модуля
3. Передача объекта из фикстуры в тесты
Фикстуры можно использовать для передачи данных или объектов в тестовые сценарии, что позволяет упростить код и повысить его читаемость.
3.1 В тестовую функцию

import pytest @pytest.fixture() def f_return_obj(): '''fixture: Возвращает объект''' print('> fixture: Запуск f_return_obj()') return 'объект из fixture' def test_1(f_return_obj): data = f_return_obj print('test1: Объект из f_return_obj() -', data) assert True def test_2(f_return_obj): data = f_return_obj print('test2: Объект из f_return_obj() -', data) assert True
Результат выполнения теста:
$: pytest -vs example.py example.py::test_1 > fixture: Запуск f_return_obj() test1: Объект из f_return_obj() - объект из fixture PASSED example.py::test_2 > fixture: Запуск f_return_obj() test2: Объект из f_return_obj() - объект из fixture PASSED
Также можно использовать одну фикстуру в других фикстурах:

import pytest @pytest.fixture() def f_pre_return_obj_1(): '''fixture: Возвращает объект''' print('>> fixture: Запуск f_pre_return_obj_1()') return 'Объект из fixture 1' @pytest.fixture() def f_pre_return_obj_2(): '''fixture: Возвращает объект''' print('>> fixture: Запуск f_pre_return_obj_2()') return 'Объект из fixture 2' @pytest.fixture() def f_return_obj(f_pre_return_obj_1, f_pre_return_obj_2): '''fixture: При вызове возвращает объект собранный из других fixture''' print('> fixture: Запуск f_return_obj()') return '{}, {}'.format(f_pre_return_obj_1, f_pre_return_obj_2) def test_1(f_return_obj): data = f_return_obj print('test1: Объекты из f_return_obj() -', data) assert True
Результат выполнения теста:
$: pytest -vs example.py example.py::test_1 >> fixture: Запуск f_pre_return_obj_1() >> fixture: Запуск f_pre_return_obj_2() > fixture: Запуск f_return_obj() test1: Объекты из f_return_obj() - Объект из fixture 1, Объект из fixture 2 PASSED
Объект в тестовую функцию можно передать и с использованием фикстуры как обертки:

import pytest @pytest.fixture() def f_wrapper_function(): '''fixture: Обертка для тестовой функции c передачей объекта''' print('>>', 'fixture: Запуск обертки f_wrapper_function()') var_fun = 'Значение объекта из f_wrapper_function()' yield var_fun print('>>', 'fixture: Завершение обертки f_wrapper_function()') def test_1(f_wrapper_function): data = f_wrapper_function print('test1:', data) assert True
Результат выполнения теста:
$: pytest -vs example.py example.py::test_1 >> fixture: Запуск обертки f_wrapper_function() test1: Значение объекта из f_wrapper_function() PASSED >> fixture: Завершение обертки f_wrapper_function()
3.2 В переменную тестового класса

import pytest @pytest.fixture(scope='class', autouse=True) def f_wrapper_class(request): '''fixture: Обертка для тестового класса, которая также создает переменную''' print('>', 'fixture: Запуск обертки f_wrapper_class()') request.cls.var1 = 'var1: значение заданное fixture' yield print('>', 'fixture: Завершение обертки f_wrapper_class()') @pytest.fixture(scope='class') def f_set_var_in_class(request): '''fixture: Задает тестовому классу переменную''' print('>> fixture: Запуск f_set_var_in_class()') request.cls.var2 = 'var2: значение заданное fixture' @pytest.mark.usefixtures('f_set_var_in_class') class TestSuite(): def test_1(self): print('TestSuite: Запуск test1') print('{}\n{}'.format(self.var1, self.var2)) self.var1 = 'var1: новое значение' self.var2 = 'var2: новое значение' print('{}\n{}'.format(self.var1, self.var2)) assert True def test_2(self): print('TestSuite: Запуск test2') print('{}\n{}'.format(self.var1, self.var2)) assert True
Результат выполнения теста:
$: pytest -vs example.py example.py::TestSuite1::test_1 > fixture: Запуск обертки f_wrapper_class() >> fixture: Запуск f_set_var_in_class() TestSuite: Запуск test1 var1: значение заданное fixture var2: значение заданное fixture var1: новое значение var2: новое значение PASSED example.py::TestSuite1::test_2 >> fixture: Запуск f_set_var_in_class() TestSuite: Запуск test2 var1: значение заданное fixture var2: значение заданное fixture PASSED > fixture: Завершение обертки f_wrapper_class()
! Обратите внимание, что при выполнении каждой тестовой функции, значения для переменных тестового класса остаются постоянными и равны значению заданного фикстурой. Примеры создания общего объекта с помощью фикстур для его использования в разных тестовых функциях представлен ниже.
4. Передача объекта между тестовыми функциями
Иногда возникает необходимость передавать один и тот же объект между разными тестовыми функциями.
4.1 Через отдельный независимый объект объект

Import pytest class BDVar(): '''Класс для хранения переменных''' def __init__(self, msg): self.var = msg def get_var(self): return self.var def set_var(self, new_var): self.var = new_var var = BDVar('var: значение по умолчанию') @pytest.fixture(scope='class') def f_wrapper_class(): '''fixture: Обертка для тестового класса, которая создает переменную''' print('>', 'fixture: Запуск обертки f_wrapper_class()') print(var.get_var()) var.set_var('var: значение заданное fixture') print(var.get_var()) yield print('>', 'fixture: Завершение обертки f_wrapper_class()') @pytest.mark.usefixtures('f_wrapper_class') class TestSuite(): def test_1(self): print('TestSuite: Запуск test1') print(var.get_var()) var.set_var('var: новое значение') print(var.get_var()) assert True def test_2(self): print('TestSuite: Запуск test2') print(var.get_var()) assert True
Результат выполнения теста:
$: pytest -vs example.py example.py::TestSuite::test_1 > fixture: Запуск обертки f_wrapper_class() var: значение по умолчанию var: значение заданное fixture TestSuite: Запуск test1 var: значение заданное fixture var: новое значение PASSED example.py::TestSuite::test_2 TestSuite: Запуск test2 var: новое значение PASSED > fixture: Завершение обертки f_wrapper_class()
4.2 Через отдельный объект внутри тестового класса

import pytest class BDVar(): '''Класс для хранения переменных''' def __init__(self, msg): self.var = msg def get_var(self): return self.var def set_var(self, new_var): self.var = new_var @pytest.fixture(scope='class') def f_wrapper_class(request): '''fixture: Обертка для тестового класса, которая создает переменную''' print('>', 'fixture: Запуск обертки f_wrapper_class()') request.cls.var = BDVar('var: значение заданное fixture') yield print('>', 'fixture: Завершение обертки f_wrapper_class()') @pytest.mark.usefixtures('f_wrapper_class') class TestSuite(): def test_1(self): print('TestSuite: Запуск test1') print(self.var.get_var()) self.var.set_var('var: новое значение') print(self.var.get_var()) assert True def test_2(self): print('TestSuite: Запуск test2') print(self.var.get_var()) assert True
Результат выполнения теста:
$: pytest -vs example.py example.py::TestSuite1::test_1 > fixture: Запуск обертки f_wrapper_class() TestSuite: Запуск test1 var: значение заданное fixture var: новое значение PASSED example.py::TestSuite1::test_2 TestSuite: Запуск test2 var: новое значение PASSED > fixture: Завершение обертки f_wrapper_class()
5. Параметризация фикстур
Фикстуры как и обычные тестовые сценарии поддерживают возможность параметризации, что позволяет использовать один тестовый сценарий с различным набором параметров.

5.1 Через параметры в фикстуре
import pytest @pytest.fixture(params=['параметр 1', 'параметр 2']) def f_wrapper_function(request): '''fixture: Возвращает объект''' print('>', 'fixture: Запуск обертки f_wrapper_function()') print('fixture: Создание объекта') data = 'Объект из fixture: {}'.format(request.param) yield data print('fixture: Завершение созданного объекта') print('>', 'fixture: Завершение обертки f_wrapper_function()') def test_1(f_wrapper_function): print('TestSuite: Запуск test1') print(f_wrapper_function) assert True def test_2(f_wrapper_function): print('TestSuite: Запуск test2') print(f_wrapper_function) assert True
Результат выполнения теста:
$: pytest -vs example.py example.py::test_1[параметр 1] > fixture: Запуск обертки f_wrapper_function() fixture: Создание объекта TestSuite: Запуск test1 Объект из fixture: параметр 1 PASSED fixture: Завершение созданног�� объекта > fixture: Завершение обертки f_wrapper_function() example.py::test_1[параметр 2] > fixture: Запуск обертки f_wrapper_function() fixture: Создание объекта TestSuite: Запуск test1 Объект из fixture: параметр 2 PASSED fixture: Завершение созданного объекта > fixture: Завершение обертки f_wrapper_function() example.py::test_2[параметр 1] > fixture: Запуск обертки f_wrapper_function() fixture: Создание объекта TestSuite: Запуск test2 Объект из fixture: параметр 1 PASSED fixture: Завершение созданного объекта > fixture: Завершение обертки f_wrapper_function() example.py::test_2[параметр 2] > fixture: Запуск обертки f_wrapper_function() fixture: Создание объекта TestSuite: Запуск test2 Объект из fixture: параметр 2 PASSED fixture: Завершение созданного объекта > fixture: Завершение обертки f_wrapper_function()
5.2 Через mark.parametrize тестовой функции
import pytest @pytest.fixture() def f_wrapper_function(request): '''fixture: Возвращает объект''' print('>', 'fixture: Запуск обертки f_wrapper_function()') print('fixture: Создание объекта') data = 'Объект из fixture: {}'.format(request.param) yield data print('fixture: Завершение созданного объекта') print('>', 'fixture: Завершение обертки f_wrapper_function()') @pytest.mark.parametrize('f_wrapper_function', ('параметр 1', 'параметр 2'), indirect=True) def test_1(f_wrapper_function): print('TestSuite: Запуск test1') print(f_wrapper_function) assert True @pytest.mark.parametrize('f_wrapper_function', ('параметр 3', 'параметр 4'), indirect=True) def test_2(f_wrapper_function): print('TestSuite: Запуск test2') print(f_wrapper_function) assert True
Результат выполнения теста:
$: pytest -vs example.py example.py::test_1[параметр 1] > fixture: Запуск обертки f_wrapper_function() fixture: Создание объекта TestSuite: Запуск test1 Объект из fixture: параметр 1 PASSED fixture: Завершение созданного объекта > fixture: Завершение обертки f_wrapper_function() example.py::test_1[параметр 2] > fixture: Запуск обертки f_wrapper_function() fixture: Создание объекта TestSuite: Запуск test1 Объект из fixture: параметр 2 PASSED fixture: Завершение созданного объекта > fixture: Завершение обертки f_wrapper_function() example.py::test_2[параметр 3] > fixture: Запуск обертки f_wrapper_function() fixture: Создание объекта TestSuite: Запуск test2 Объект из fixture: параметр 3 PASSED fixture: Завершение созданного объекта > fixture: Завершение обертки f_wrapper_function() example.py::test_2[параметр 4] > fixture: Запуск обертки f_wrapper_function() fixture: Создание объекта TestSuite: Запуск test2 Объект из fixture: параметр 4 PASSED fixture: Завершение созданного объекта >> fixture: Завершение обертки f_wrapper_function()
6. Передача параметров из тестовых сценариев в фикстуру для генерации на их основе объектов для выполнения тестов
Вынесения параметров которые используют фикстуры в тестовые сценарии позволяет создавать универсальные фикстуры и делать код более универсальным.
6.1 Через pytest.mark

import pytest @pytest.fixture() def f_cfg_obj(request): '''fixture: Получает данные перед выполнением тестовой функции''' print('>', 'fixture: Запуск f_set_obj()') marker = request.node.get_closest_marker('f_data') data = None if marker is None else marker.args[0] print('fixture:', data) return data.replace('Переданный', 'Измененый').replace('в', 'от') @pytest.mark.f_data('Переданный параметр в fixture') def test_1(f_cfg_obj): print('test1:', f_cfg_obj) assert True
Результат выполнения теста:
$: pytest -vs example.py example.py::test_1 > fixture: Запуск f_set_obj() fixture: Переданный параметр в fixture test1: Измененый параметр от fixture PASSED
6.2 Через метод внутри fixture

import pytest @pytest.fixture() def f_cfg_obj(request): '''fixture: Через метод возвращает объект''' print('>', 'fixture: Запуск f_set_obj()') def _set_cfg(cfg): print('>>', 'fixture: Запуск _set_cfg()') return cfg.replace('переданный в', 'измененый от') return _set_cfg def test_1(f_cfg_obj): data_1 = f_cfg_obj('переданный в fixture параметр-1') data_2 = f_cfg_obj('переданный в fixture параметр-2') print('test1:\n- {}\n- {}'.format(data_1, data_2)) assert True
Результат выполнения теста:
$: pytest -vs example.py example.py::test_1 > fixture: Запуск f_set_obj() >> fixture: Запуск _set_cfg() >> fixture: Запуск _set_cfg() test1: - измененый от fixture параметр-1 - измененый от fixture параметр-2 PASSED
6.3 Через отдельный объект внутри тестового класса

import pytest class BDVar(): '''Класс для хранения переменных''' def __init__(self, msg): self.var = msg def get_var(self): return self.var def set_var(self, new_var): self.var = new_var @pytest.fixture(scope='class', autouse=True) def f_wrapper_class(request): '''fixture: Обертка для тестового класса, которая создает переменную''' print('>', 'fixture: Запуск обертки f_wrapper_class()') request.cls.var = BDVar('var: значение заданное fixture') yield print('>', 'fixture: Завершение обертки f_wrapper_class()') @pytest.fixture() def f_cfg_obj(request): '''fixture: Получает объект из переменной класа перед выполнением тестовой функции''' print('>>', 'fixture: Запуск f_set_obj') print('fixture:', request.cls.var.get_var()) request.cls.var.set_var('var: новое значение заданное в fixture') print('fixture:', request.cls.var.get_var()) class TestSuite(): def test_1(self): print('TestSuite: Запуск test1') print(self.var.get_var()) self.var.set_var('var: новое значение') print(self.var.get_var()) assert True def test_2(self, f_cfg_obj): print('TestSuite: Запуск test2') f_cfg_obj print(self.var.get_var()) assert True
Результат выполнения теста:
$: pytest -vs example.py example8.py::TestSuite::test_1 > fixture: Запуск обертки f_wrapper_class() TestSuite: Запуск test1 var: значение заданное fixture var: новое значение PASSED example8.py::TestSuite::test_2 >> fixture: Запуск f_set_obj fixture: var: новое значение fixture: var: новое значение заданное в fixture TestSuite: Запуск test2 var: новое значение заданное в fixture PASSED > fixture: Завершение обертки f_wrapper_class()
7. Финализация
Тестирование можно разбить на следующие этапы:
выполнение действий, до проведения тестирования (создание тестовых пользователей, открытие сетевого подключения, открытие файла и пр.)
тестирование
выполнение действий, после проведения тестирования (удаление тестовых пользователей, закрытие сетевого подключения, закрытие файла и пр.)
Финализация позволяет корректно выполнить последний этап тестирования при ошибках в тестовых сценариях или остановки тестирования.

7.1 Через обертку для тестовой функции
import pytest @pytest.fixture() def f_wrapper_function(): '''fixture: Обертка для тестовой функции, которая возвращает объект''' print('>', 'fixture: Запуск обертки f_wrapper_function()') print('- Создание объекта в fixture') data = 'Объект из fixture' yield data print('- Завершение созданного объекта:', data) print('>', 'fixture: Завершение обертки f_wrapper_function()') def test_1(f_wrapper_function): print('TestSuite: Запуск test1') print('-', f_wrapper_function) assert True def test_2(f_wrapper_function): print('TestSuite: Запуск test2') print('-', f_wrapper_function) assert False
Результат выполнения теста:
$: pytest -vs example.py example.py::test_1 > fixture: Запуск обертки f_wrapper_function() - Создание объекта в fixture TestSuite: Запуск test1 - Объект из fixture PASSED - Завершение созданного объекта: Объект из fixture > fixture: Завершение обертки f_wrapper_function() example.py::test_2 > fixture: Запуск обертки f_wrapper_function() - Создание объекта в fixture TestSuite: Запуск test2 - Объект из fixture FAILED - Завершение созданного объекта: Объект из fixture > fixture: Завершение обертки f_wrapper_function()
7.2 Через метод внутри фикстуры с использованием addfinalizer
import pytest @pytest.fixture(scope='function') def f_return_obj(request): '''fixture: Возвращает объект''' print('>', 'fixture: Запуск f_return_obj()') print('- Создание объекта в fixture') data = 'Объект из fixture' # Функция корректного завершения объекта def del_object(): print('- Завершение созданного объекта:', data) # Финализация request.addfinalizer(del_object) return data def test_1(f_return_obj): print('TestSuite: Запуск test1') print('-', f_return_obj) assert True def test_2(f_return_obj): print('TestSuite: Запуск test2') print('-', f_return_obj) assert False
Результат выполнения теста:
$: pytest -vs example.py example.py::test_1 > fixture: Запуск f_return_obj() - Создание объекта в fixture TestSuite: Запуск test1 - Объект из fixture PASSED - Завершение созданного объекта: Объект из fixture example.py::test_2 > fixture: Запуск f_return_obj() - Создание объекта в fixture TestSuite: Запуск test2 - Объект из fixture FAILED - Завершение созданного объекта: Объект из fixture
7.3 Через отдельный объект внутри класса
Если необходимо создать отдельный объект для класса и в конце его корректно завершить, то можно использовать следующий способ:
import pytest class Сonnect(): '''Класс для соединения''' def __init__(self, config): self.cfg = config self.status = 'Объект создан' def get_cfg(self): return self.cfg def get_status(self): return self.status def set_status(self, new_status): self.status = new_status @pytest.fixture(scope='class') def f_wrapper_class(request): '''fixture: Обертка для тестового класса''' print('>', 'fixture: Запуск обертки f_wrapper_class()') print('- Создание объекта') request.cls.connect = Сonnect('Параметры соединения') print('- config (fixture):', request.cls.connect.get_cfg()) print('- status (fixture):', request.cls.connect.get_status()) request.cls.connect.set_status('Соединение установлено') print('- status (fixture):', request.cls.connect.get_status()) yield request.cls.connect.set_status('Соединение разорвано') print('- status (fixture):', request.cls.connect.get_status()) print('>', 'fixture: Завершение обертки f_wrapper_class()') @pytest.mark.usefixtures('f_wrapper_class') class TestSuite(): def test_1(self): print('TestSuite: Запуск test1') print('- status (fun):', self.connect.get_status()) self.connect.set_status('Работа выполнена') print('- status (fun):', self.connect.get_status()) assert True def test_2(self): print('TestSuite: Запуск test2') print('\n- status (fun):', self.connect.get_status()) self.connect.set_status('В работе возникла ошибка') print('- status (fun):', self.connect.get_status()) assert False
Результат выполнения теста:
$: pytest -vs example.py example.py::TestSuite::test_1 > fixture: Запуск обертки f_wrapper_class() - Создание объекта - config (fixture): Параметры соединения - status (fixture): Объект создан - status (fixture): Соединение установлено TestSuite: Запуск test1 - status (fun): Соединение установлено - status (fun): Работа выполнена PASSED example.py::TestSuite::test_2 TestSuite: Запуск test2 - status (fun): Работа выполнена - status (fun): В работе возникла ошибка FAILED - status (fixture): Соединение разорвано > fixture: Завершение обертки f_wrapper_class()
