💻 Примеры: Calculator, pytest, маркировки, параметризация

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

# 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):
        calculator.div(10, 0)

@pytest.mark.parametrize('a,b,res', [(4,5,9),(-6,-10,-16),(10,0,10)])
def test_sum_param(a, b, res):
    assert Calculator().sum(a, b) == res

Пример 1: Класс Calculator

# Lesson1/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):
        if b == 0:
            raise ArithmeticError("На ноль делить нельзя")
        return a / b

    def pow(self, a, b=2):
        return a ** b

    def avg(self, nums):
        if len(nums) == 0:
            return 0
        s = sum(nums)
        return self.div(s, len(nums))

Пример 2: Тесты с фикстурой

# Lesson1/test_calculator.py
import pytest
from calculator import Calculator

@pytest.fixture
def 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

Пример 3: Маркировки тестов

# Lesson1/test_calculator_marks.py
import pytest
from calculator import Calculator

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

# Условный пропуск
import sys
@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():
    calculator = Calculator()
    assert calculator.sum(4, 5) == 9

@pytest.mark.positive_test
def test_div_positive():
    calculator = Calculator()
    assert calculator.div(10, 2) == 5
Запуск только positive_test:
pytest -m positive_test
Вывод:
collected 9 items / 7 deselected / 2 selected
PASSED test_calculator_marks.py::test_sum_positive
PASSED test_calculator_marks.py::test_div_positive

Пример 4: Конфигурация pytest.ini — выборочный запуск

# pytest.ini (в корне проекта)
[pytest]
python_functions = *sum*

markers =
    positive_test: Тесты для позитивных сценариев
    negative_test: Тесты для негативных сценариев
После добавления этой конфигурации команда pytest запустит только тесты, содержащие слово sum в имени.

Пример 5: Параметризация тестов

# Lesson1/test_calculator_parametrize.py
import pytest
from calculator import Calculator

@pytest.mark.parametrize('num1, num2, result', [
    (4, 5, 9),
    (-6, -10, -16),
    (-6, 6, 0),
    (5.61, 4.29, 9.9),
    (10, 0, 10)
])
def test_sum(num1, num2, result):
    calculator = Calculator()
    res = calculator.sum(num1, num2)
    assert res == result

@pytest.mark.parametrize('nums, result', [
    ([], 0),                            # Пустой список
    ([1, 2, 3, 4, 5, 6, 7, 8, 9, 5], 5)  # Среднее арифметическое
])
def test_avg(nums, result):
    calculator = Calculator()
    res = calculator.avg(nums)
    assert res == result
Вывод pytest -v:
test_calculator_parametrize.py::test_sum[4-5-9] PASSED
test_calculator_parametrize.py::test_sum[-6--10--16] PASSED
test_calculator_parametrize.py::test_sum[-6-6-0] PASSED
test_calculator_parametrize.py::test_sum[5.61-4.29-9.9] PASSED
test_calculator_parametrize.py::test_sum[10-0-10] PASSED
test_calculator_parametrize.py::test_avg[nums0-0] PASSED
test_calculator_parametrize.py::test_avg[nums1-5] PASSED

Пример 6: Разбор домашнего задания из лекции

В лекции разбирали класс SimpleMath — ДЗ урока 01.
# simple_math.py
class SimpleMath:
    def square(self, n):
        return n ** 2

    def cube(self, n):
        return n ** 3
# test_simple_math.py
from simple_math import SimpleMath

math = SimpleMath()

def test_square_positive():
    """Тест метода square для положительного числа."""
    assert math.square(2) == 4

def test_square_negative():
    """Тест метода square для отрицательного числа."""
    assert math.square(-3) == 9

def test_square_zero():
    """Тест метода square для нуля."""
    assert math.square(0) == 0

def test_cube_positive():
    """Тест метода cube для положительного числа."""
    assert math.cube(3) == 27

def test_cube_negative():
    """Тест метода cube для отрицательного числа."""
    assert math.cube(-2) == -8

def test_cube_zero():
    """Тест метода cube для нуля."""
    assert math.cube(0) == 0