⚖️ Старый vs Новый: подходы к unit-тестированию

⚡ Кратко: три подхода

  • Ручной assert (из лекции) — скрипт, падает на первом баге. Только для обучения.
  • unittest.TestCase — встроен в Python, подходит для устаревших проектов. Много boilerplate.
  • pytest (современный) — обычные функции, простой assert, фикстуры, маркировки. Рекомендуется.

Три подхода к тестированию

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

Подход 1: Ручной assert (из лекции — начальный этап)

Лекция начинается с ручного тестирования: создаёт экземпляр Calculator и вызывает assert для каждого случая. Это учебный инструмент для понимания концепции.

# calculator_test.py  (ручной подход — из лекции)
from calculator import Calculator

calculator = Calculator()

res = calculator.sum(4, 5)
assert res == 9

res = calculator.sum(-6, -10)
assert res == -16

# ... и так далее для каждого случая

print("Все тесты прошли!")
Проблема: при падении одного assert скрипт немедленно останавливается. Нет никакого отчёта — сколько тестов прошло, сколько упало. Каждый раз нужно запускать вручную.

Подход 2: unittest.TestCase (встроенный модуль Python)

Модуль unittest входит в стандартную библиотеку Python. Используется в старых проектах и корпоративных кодовых базах.

# test_calculator_unittest.py
import unittest
from calculator import Calculator


class TestCalculator(unittest.TestCase):

    def setUp(self):
        """setUp вызывается перед каждым тестом — аналог фикстуры pytest."""
        self.calculator = Calculator()

    def test_sum_positive_numbers(self):
        result = self.calculator.sum(4, 5)
        self.assertEqual(result, 9)

    def test_sum_negative_numbers(self):
        result = self.calculator.sum(-6, -10)
        self.assertEqual(result, -16)

    def test_division_by_zero(self):
        with self.assertRaises(ArithmeticError):
            self.calculator.div(10, 0)

    def test_avg_empty_list(self):
        self.assertEqual(self.calculator.avg([]), 0)


if __name__ == "__main__":
    unittest.main()
# Запуск unittest:
python -m unittest test_calculator_unittest.py -v

# Вывод:
# test_avg_empty_list (test_calculator_unittest.TestCalculator) ... ok
# test_division_by_zero (test_calculator_unittest.TestCalculator) ... ok
# test_sum_negative_numbers (test_calculator_unittest.TestCalculator) ... ok
# test_sum_positive_numbers (test_calculator_unittest.TestCalculator) ... ok
#
# Ran 4 tests in 0.002s
# OK
Устаревшие методы assertEqual: в старом коде часто встречается assertEquals (с s) — это алиас, который существует, но устарел. Современный вариант — assertEqual (без s).

Подход 3: pytest (современный — рекомендуется)

Именно этот подход используется в лекции как целевой. pytest работает с обычными функциями Python и обычным assert.

# test_calculator.py  (pytest — современный)
import pytest
from calculator import Calculator


@pytest.fixture
def calculator():
    return Calculator()


def test_sum_positive_numbers(calculator):
    assert calculator.sum(4, 5) == 9

def test_sum_negative_numbers(calculator):
    assert calculator.sum(-6, -10) == -16

def test_division_by_zero(calculator):
    with pytest.raises(ArithmeticError, match="На ноль делить нельзя"):
        calculator.div(10, 0)

def test_avg_empty_list(calculator):
    assert calculator.avg([]) == 0

Сравнительная таблица

Критерий Ручной assert unittest.TestCase pytest
Установка Не нужна Не нужна (stdlib) pip install pytest
Синтаксис теста Скрипт с assert Класс + self.assert*() Функция + assert
Изоляция тестов Нет setUp/tearDown @pytest.fixture
Отчёт при падении Нет (стоп на первом) Да Да (подробный)
Маркировки / группировка Нет Базовые Мощные (@mark.*)
Применение Только обучение Старые проекты, CI Новые проекты ✓
Совет: pytest обратно совместим с unittest — он умеет запускать unittest.TestCase-тесты. В реальных проектах часто встречается смесь обоих подходов.

← К оглавлению урока