В данной статье представлены примеры использования фикстур (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()