Пример 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