Unittest - стандартная библиотека Python для тестирования
Unittest - это встроенная в Python библиотека для создания и выполнения модульных тестов. Она предоставляет полный набор инструментов для написания структурированных тестов, организации проверок и управления тестовыми данными.
Основные концепции unittest
TestCase - базовый класс для создания тестов. Все тестовые классы должны наследоваться от unittest.TestCase.
setUp() и tearDown() - специальные методы, которые автоматически выполняются до и после каждого теста. Используются для подготовки тестовых данных и их очистки.
Assertions - методы для проверки условий и результатов выполнения кода. Включают assertEqual(), assertTrue(), assertFalse(), assertRaises() и другие.
Практический пример unittest
import unittest
def add(x, y):
"""Функция сложения двух чисел"""
return x + y
def divide(x, y):
"""Функция деления с проверкой на ноль"""
if y == 0:
raise ValueError("Деление на ноль невозможно")
return x / y
class TestMathFunctions(unittest.TestCase):
def setUp(self):
"""Подготовка данных перед каждым тестом"""
self.test_value = 42
self.test_list = [1, 2, 3, 4, 5]
def tearDown(self):
"""Очистка данных после каждого теста"""
del self.test_value
del self.test_list
def test_addition_positive(self):
"""Тест сложения положительных чисел"""
result = add(5, 3)
self.assertEqual(result, 8)
def test_addition_negative(self):
"""Тест сложения отрицательных чисел"""
result = add(-2, -3)
self.assertEqual(result, -5)
def test_setup_value(self):
"""Проверка значения из setUp"""
self.assertEqual(self.test_value, 42)
self.assertIsInstance(self.test_value, int)
def test_list_operations(self):
"""Тест операций со списком"""
self.assertIn(3, self.test_list)
self.assertEqual(len(self.test_list), 5)
self.assertTrue(all(isinstance(x, int) for x in self.test_list))
def test_division_by_zero(self):
"""Тест исключения при делении на ноль"""
with self.assertRaises(ValueError):
divide(10, 0)
def test_division_normal(self):
"""Тест обычного деления"""
result = divide(10, 2)
self.assertEqual(result, 5.0)
if __name__ == '__main__':
unittest.main()
Основные методы assertions в unittest
assertEqual(a, b)- проверяет равенство значенийassertNotEqual(a, b)- проверяет неравенствоassertTrue(x)- проверяет истинность условияassertFalse(x)- проверяет ложность условияassertIs(a, b)- проверяет идентичность объектовassertIsNone(x)- проверяет на NoneassertIn(a, b)- проверяет вхождение элементаassertRaises(exc, fun, *args)- проверяет возникновение исключения
Pytest - современный фреймворк для тестирования
Pytest - это мощный и гибкий фреймворк для тестирования Python-приложений. Он отличается простотой синтаксиса, богатой функциональностью и обширной экосистемой плагинов.
Установка pytest
pip install pytest
Основы pytest
# test_math.py
def add(x, y):
"""Функция сложения"""
return x + y
def multiply(x, y):
"""Функция умножения"""
return x * y
def test_add_positive():
"""Тест сложения положительных чисел"""
assert add(2, 3) == 5
def test_add_negative():
"""Тест сложения отрицательных чисел"""
assert add(-1, -1) == -2
def test_add_zero():
"""Тест сложения с нулем"""
assert add(5, 0) == 5
def test_multiply():
"""Тест умножения"""
assert multiply(3, 4) == 12
assert multiply(-2, 3) == -6
Фикстуры (fixtures) в pytest
Фикстуры позволяют подготавливать данные и ресурсы для тестов, а также управлять их жизненным циклом.
import pytest
@pytest.fixture
def sample_data():
"""Фикстура с тестовыми данными"""
return {"name": "Python", "version": "3.9", "type": "language"}
@pytest.fixture
def temp_file():
"""Фикстура для создания временного файла"""
import tempfile
import os
# Создание временного файла
fd, path = tempfile.mkstemp()
yield path
# Очистка после теста
os.close(fd)
os.unlink(path)
@pytest.fixture(scope="session")
def database_connection():
"""Фикстура уровня сессии для соединения с БД"""
# Подключение к базе данных
connection = "fake_db_connection"
yield connection
# Закрытие соединения
print("Закрытие соединения с БД")
def test_data_access(sample_data):
"""Тест с использованием фикстуры"""
assert sample_data["name"] == "Python"
assert sample_data["version"] == "3.9"
def test_file_operations(temp_file):
"""Тест работы с файлом"""
with open(temp_file, 'w') as f:
f.write("test content")
with open(temp_file, 'r') as f:
content = f.read()
assert content == "test content"
Параметризация тестов
import pytest
@pytest.mark.parametrize("a,b,expected", [
(1, 2, 3),
(0, 0, 0),
(-1, 1, 0),
(10, -5, 5),
])
def test_add_parametrized(a, b, expected):
"""Параметризованный тест сложения"""
assert add(a, b) == expected
@pytest.mark.parametrize("value,expected", [
("hello", True),
("", False),
("python", True),
(None, False),
])
def test_string_validation(value, expected):
"""Тест валидации строк"""
def is_valid_string(s):
return bool(s and isinstance(s, str))
assert is_valid_string(value) == expected
Маркировка тестов
import pytest
@pytest.mark.slow
def test_slow_operation():
"""Медленный тест"""
import time
time.sleep(2)
assert True
@pytest.mark.fast
def test_fast_operation():
"""Быстрый тест"""
assert 1 + 1 == 2
@pytest.mark.integration
def test_database_integration():
"""Интеграционный тест"""
assert True
@pytest.mark.unit
def test_unit_function():
"""Модульный тест"""
assert len("test") == 4
# Запуск тестов по маркерам:
# pytest -m fast # только быстрые тесты
# pytest -m slow # только медленные тесты
# pytest -m "not slow" # все кроме медленных
Популярные плагины pytest
# pytest-cov - измерение покрытия кода
# Установка: pip install pytest-cov
# Запуск: pytest --cov=my_module tests/
# pytest-html - HTML отчеты
# Установка: pip install pytest-html
# Запуск: pytest --html=report.html
# pytest-xdist - параллельное выполнение
# Установка: pip install pytest-xdist
# Запуск: pytest -n 4 # 4 параллельных процесса
Продвинутые возможности pytest
import pytest
class TestAdvancedFeatures:
"""Класс для демонстрации продвинутых возможностей"""
def test_approximate_comparison(self):
"""Тест приблизительного сравнения"""
assert 0.1 + 0.2 == pytest.approx(0.3)
assert [1.0, 2.0, 3.0] == pytest.approx([1.01, 2.01, 3.01], abs=0.1)
def test_exception_handling(self):
"""Тест обработки исключений"""
with pytest.raises(ValueError, match="invalid literal"):
int("not_a_number")
def test_warnings(self):
"""Тест предупреждений"""
import warnings
with pytest.warns(UserWarning):
warnings.warn("This is a warning", UserWarning)
@pytest.mark.skip(reason="Функция еще не реализована")
def test_not_implemented(self):
"""Пропущенный тест"""
pass
@pytest.mark.skipif(sys.version_info < (3, 8), reason="Требует Python 3.8+")
def test_python_version_specific(self):
"""Тест для конкретной версии Python"""
assert True
Сравнение unittest и pytest
Преимущества unittest
- Встроенность: входит в стандартную библиотеку Python, не требует установки
- Стабильность: долгая история разработки и широкая поддержка
- Структурированность: четкая организация тестов в классы и методы
- Совместимость: работает во всех средах Python без дополнительных зависимостей
Преимущества pytest
- Простота синтаксиса: минимальный код для написания тестов
- Мощные фикстуры: гибкая система подготовки данных
- Богатая экосистема: множество плагинов для расширения функциональности
- Параметризация: легкое создание множественных тестов
- Информативные сообщения: подробные отчеты о неудачных тестах
- Автоматическое обнаружение: находит и запускает тесты без дополнительной настройки
Когда использовать unittest
- Проекты, где важна минимизация зависимостей
- Корпоративные среды с ограничениями на внешние библиотеки
- Команды, предпочитающие объектно-ориентированный подход к тестированию
- Проекты, требующие сложной иерархии тестов
Когда использовать pytest
- Новые проекты, где можно выбирать инструменты
- Команды, ценящие простоту и скорость разработки
- Проекты, требующие сложной параметризации тестов
- Случаи, когда нужны специализированные возможности (покрытие кода, параллельное выполнение)
Лучшие практики тестирования
Структура проекта
project/
├── src/
│ └── my_module/
│ └── __init__.py
├── tests/
│ ├── __init__.py
│ ├── test_unit.py
│ ├── test_integration.py
│ └── conftest.py # для pytest фикстур
├── requirements.txt
└── setup.py
Рекомендации по написанию тестов
- Именование: используйте описательные имена для тестов
- Изоляция: каждый тест должен быть независимым
- Один тест - одна проверка: фокусируйтесь на конкретной функциональности
- Подготовка данных: используйте фикстуры для создания тестовых данных
- Покрытие кода: стремитесь к высокому покрытию, но не забывайте о качестве тестов
Выбор между unittest и pytest зависит от требований проекта, предпочтений команды и сложности тестируемого кода. Оба фреймворка обеспечивают надежное тестирование Python-приложений и имеют свои уникальные преимущества.