Каковы различия между фреймворками Pytest и unittest в Python?

Ответ

unittest — это встроенный в Python фреймворк для модульного тестирования, являющийся частью стандартной библиотеки. pytest — популярный сторонний фреймворк, предлагающий более современный и гибкий подход к написанию тестов. Оба используются для написания и запуска тестов, но имеют существенные различия в синтаксисе, функциональности и экосистеме.

1. Синтаксис написания тестов

  • unittest: Требует создания классов, наследующихся от unittest.TestCase, и использования специальных методов assert* (например, assertEqual(), assertTrue()). Это может приводить к более многословному коду.

    import unittest
    
    class TestMath(unittest.TestCase):
        def test_addition(self):
            self.assertEqual(1 + 1, 2)
    
        def test_subtraction(self):
            self.assertTrue(5 - 3 == 2)
  • pytest: Позволяет писать тесты как обычные функции или методы, используя стандартный оператор assert Python. Это делает тесты более читаемыми, лаконичными и интуитивно понятными, так как не требует изучения специфических методов.

    # test_math.py
    def test_addition():
        assert 1 + 1 == 2
    
    def test_subtraction():
        assert 5 - 3 == 2

2. Фикстуры (Fixtures)

  • unittest: Использует методы setUp() и tearDown() (или setUpClass/tearDownClass) для подготовки и очистки тестового окружения. Они привязаны к жизненному циклу класса или метода и могут быть менее гибкими для переиспользования.

    import unittest
    
    class TestDatabase(unittest.TestCase):
        def setUp(self):
            # Подключение к БД перед каждым тестом
            self.db_conn = connect_to_db()
    
        def tearDown(self):
            # Закрытие соединения после каждого теста
            self.db_conn.close()
    
        def test_query(self):
            result = self.db_conn.execute("SELECT 1")
            self.assertEqual(result, 1)
  • pytest: Предлагает более гибкую и мощную систему фикстур с помощью декоратора @pytest.fixture. Фикстуры могут быть функциями, возвращающими данные, и внедряться в тесты по имени. Это позволяет легко переиспользовать логику подготовки/очистки, управлять их областью видимости (функция, модуль, сессия) и создавать сложные зависимости.

    import pytest
    
    @pytest.fixture
    def db_connection():
        # Подготовка ресурса (например, подключение к БД)
        conn = connect_to_db()
        yield conn # Тесты, использующие эту фикстуру, выполняются здесь
        # Очистка ресурса после выполнения тестов
        conn.close()
    
    def test_query(db_connection):
        result = db_connection.execute("SELECT 1")
        assert result == 1

3. Параметризация тестов

  • unittest: Параметризация сложнее, часто требует использования subTest (для Python 3.4+) или внешних библиотек, что может усложнить код.
  • pytest: Встроенная и мощная поддержка параметризации с помощью декоратора @pytest.mark.parametrize, что позволяет легко запускать один и тот же тест с разными наборами входных данных, значительно сокращая дублирование кода.

    import pytest
    
    @pytest.mark.parametrize("a, b, expected", [
        (1, 1, 2),
        (2, 3, 5),
        (0, 0, 0),
    ])
    def test_addition(a, b, expected):
        assert a + b == expected

4. Отчеты и плагины

  • unittest: Генерирует базовые отчеты. Расширение функциональности часто требует написания кастомных тестовых раннеров.
  • pytest: Генерирует более информативные и читаемые отчеты по умолчанию, включая подробную информацию о сбоях. Имеет богатую экосистему плагинов (например, pytest-cov для покрытия кода, pytest-xdist для параллельного выполнения, pytest-mock для моков), что значительно расширяет его возможности и гибкость.

Вывод

pytest часто предпочтительнее для новых проектов благодаря своей простоте, гибкости, мощной системе фикстур и обширной экосистеме плагинов, что способствует написанию более чистого и эффективного тестового кода. unittest остается надежным выбором для базового модульного тестирования, особенно в проектах, где он уже используется или когда требуется строгая совместимость со стандартной библиотекой.

Ответ 18+ 🔞

Ну слушай, тут тебе на пальцах объяснят, в чём разница между этими двумя штуками для проверки кода. Представь, что unittest — это как старый, добрый, но местами занудный дед, который всё делает по уставу. А pytest — это такой шустрый пацан, который пришёл и говорит: «Да зачем так сложно-то, можно же проще!».

1. Как тесты писать вообще

С unittest тебе надо, блядь, целый класс заводить, который от какого-то там TestCase наследуется. И вместо того чтобы просто написать assert 1+1 == 2, ты должен писать self.assertEqual(1+1, 2). Ну, ёпта, зачем столько букв? Это ж как на параде строем ходить.

import unittest

class TestMath(unittest.TestCase):
    def test_addition(self):
        self.assertEqual(1 + 1, 2)  # О, боже, какой формализм!

А pytest — это просто праздник какой-то. Написал обычную функцию, внутри обычный assert, и всё, пошла писать губерния. Никаких лишних телодвижений.

def test_addition():
    assert 1 + 1 == 2  # Всё, сука, элементарно. Никакой магии.

2. Фикстуры, или как подготовить всё для теста

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

import unittest

class TestDatabase(unittest.TestCase):
    def setUp(self):
        self.db_conn = connect_to_db()  # Подключились

    def tearDown(self):
        self.db_conn.close()  # Отключились

А pytest подходит к делу с умом. Ты создаёшь отдельную функцию, вешаешь на неё волшебный декоратор @pytest.fixture, и потом просто говоришь в тесте: «Дай-ка мне сюда эту фикстуру». И она тебе подсунуется. И самое охуенное — ты можешь сделать так, чтобы фикстура работала на всю сессию тестов, а не пересоздавалась каждый раз. Красота!

import pytest

@pytest.fixture
def db_connection():
    conn = connect_to_db()
    yield conn  # Вот тут тесты выполняются
    conn.close()  # А вот тут всё прибирается

def test_query(db_connection):  # Смотри-ка, фикстура сама пришла!
    result = db_connection.execute("SELECT 1")
    assert result == 1

3. Параметризация, или как одним выстрелом убить кучу зайцев

В unittest чтобы прогнать один тест с разными данными, надо либо городить циклы, либо использовать subTest. Не сказать чтобы очень удобно.

А pytest делает это одной строчкой. Навесил декоратор @pytest.mark.parametrize, перечислил варианты — и сиди, радуйся. Один тест, а проверяет кучу случаев.

import pytest

@pytest.mark.parametrize("a, b, expected", [
    (1, 1, 2),
    (2, 3, 5),
    (0, 0, 0),
])
def test_addition(a, b, expected):
    assert a + b == expected  # И не надо три одинаковых функции писать!

4. Отчёты и разные плюшки

unittest выдаёт отчёт, ну... как умеет. Сухо, информативно, но без изысков.

А pytest, ёпта, когда тест падает, так красиво всё распишет, что иногда даже смешно становится. Плюс к нему есть овердохуища плагинов: для проверки покрытия кода, для параллельного запуска, для создания моков. Хочешь — в облако результаты отправляй.

Итог, блядь

Если у тебя старый проект на unittest и всё работает — можешь не париться. Но если начинаешь что-то новое или хочешь, чтобы тесты писались быстро и читались легко — бери pytest. Он просто берёт и делает сложные вещи простыми, а это, в рот меня чих-пых, дорогого стоит.