💻 Примеры: класс Calculator и тесты pytest

⚡ Ключевые примеры

# calculator.py
class Calculator:
    def sum(self, a, b): return a + b
    def div(self, a, b):
        if b == 0: raise ArithmeticError("На ноль делить нельзя")
        return a / b

# test_calculator.py
import pytest
from calculator import Calculator

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

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

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

Пример 1: Класс Calculator (тестируемый код)

Весь урок строится вокруг этого класса — его пишут на занятии в PyCharm, затем тестируют.

# calculator.py

class Calculator:

    def sum(self, a, b):
        """Возвращает сумму двух чисел."""
        return a + b

    def sub(self, a, b):
        """Возвращает разность двух чисел."""
        return a - b

    def mul(self, a, b):
        """Возвращает произведение двух чисел."""
        return a * b

    def div(self, a, b):
        """Делит a на b. Вызывает ArithmeticError при делении на ноль."""
        if b == 0:
            raise ArithmeticError("На ноль делить нельзя")
        return a / b

    def pow(self, a, b=2):
        """Возводит a в степень b (по умолчанию — квадрат)."""
        return a ** b

    def avg(self, nums):
        """Среднее арифметическое списка. Возвращает 0 для пустого списка."""
        if len(nums) == 0:
            return 0
        s = sum(nums)  # встроенная функция Python
        return self.div(s, len(nums))

Пример 2: Ручное тестирование через assert (подход из лекции)

Первый этап лекции — тестировать вручную, чтобы понять ограничения. При ошибке скрипт падает на первом же assert.

# calculator_test.py  (ручной вариант — НЕ pytest)
from calculator import Calculator

calculator = Calculator()

# --- Тесты метода sum ---
res = calculator.sum(4, 5)
assert res == 9                    # Положительные числа

res = calculator.sum(-6, -10)
assert res == -16                  # Отрицательные числа

res = calculator.sum(-6, 6)
assert res == 0                    # Положительное + отрицательное

res = calculator.sum(5.6, 4.3)
res = round(res, 1)
assert res == 9.9                  # Дробные числа

res = calculator.sum(10, 0)
assert res == 10                   # Число + ноль

# --- Тесты метода div ---
res = calculator.div(10, 2)
assert res == 5                    # Обычное деление

try:
    calculator.div(10, 0)
except ArithmeticError as e:
    assert str(e) == "На ноль делить нельзя"   # Деление на ноль

# --- Тесты метода avg ---
numbers = []
res = calculator.avg(numbers)
assert res == 0                    # Пустой список

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 5]
res = calculator.avg(numbers)
assert res == 5                    # Список чисел

print("Все тесты прошли!")         # Выводится только если всё ок
Ограничения ручного подхода: скрипт останавливается на первом упавшем assert. Нет отчёта — сколько тестов прошло, сколько упало. Нет изоляции между тестами. Именно поэтому переходим к pytest.

Пример 3: Тесты на pytest с фикстурой (современный подход)

# test_calculator.py
import pytest
from calculator import Calculator


@pytest.fixture
def calculator():
    """Фикстура: создаёт экземпляр Calculator для каждого теста."""
    return Calculator()


# --- Тесты метода sum ---

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_sum_positive_and_negative(calculator):
    assert calculator.sum(-6, 6) == 0

def test_sum_floats(calculator):
    res = calculator.sum(5.6, 4.3)
    assert round(res, 1) == 9.9

def test_sum_with_zero(calculator):
    assert calculator.sum(10, 0) == 10


# --- Тесты метода div ---

def test_division(calculator):
    assert calculator.div(10, 2) == 5

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


# --- Тесты метода avg ---

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

def test_avg_list(calculator):
    numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 5]
    assert calculator.avg(numbers) == 5

Пример 4: Запуск и результаты

# Терминал (в папке Lesson1):
pytest

# Вывод — все 9 тестов проходят:
# =========================================================
# test session starts
# =========================================================
# collected 9 items
#
# test_calculator.py .........                        [100%]
#
# ========================================================= 9 passed in 0.05s

# Запуск только тестов на сложение:
pytest -k "sum"

# =========================================================
# collected 9 items / 4 deselected / 5 selected
# test_calculator.py .....                            [100%]
# ============================= 5 passed, 4 deselected in 0.04s

Пример 5: Итоговый файл с маркировками

# test_calculator_marks.py
import sys
import pytest
from calculator import Calculator


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


@pytest.mark.skip(reason="Тест будет исправлен позже")
def test_sum_positive_nums():
    calculator = Calculator()
    res = calculator.sum(4, 5)
    assert res == 9


@pytest.mark.skipif(sys.version_info < (3, 8), reason="Требуется Python 3.8 или выше")
def test_sum_negative_nums():
    calculator = Calculator()
    res = calculator.sum(-6, -10)
    assert res == -16


@pytest.mark.xfail(strict=True, reason="Метод в процессе разработки")
def test_sum_with_error():
    calculator = Calculator()
    res = calculator.sum(4, 5)
    assert res == 10   # Умышленная ошибка


@pytest.mark.positive_test
def test_sum_positive_nums_v2(calculator):
    assert calculator.sum(4, 5) == 9


@pytest.mark.positive_test
def test_div_positive(calculator):
    assert calculator.div(10, 2) == 5
# Запуск всех тестов с маркировкой positive_test:
pytest -m positive_test -v

# Вывод:
# collected 9 items / 7 deselected / 2 selected
# PASSED test_calculator_marks.py::test_sum_positive_nums_v2
# PASSED test_calculator_marks.py::test_div_positive

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