Deep Engineering

ЗАМЕР

bench/testing/pytest_run.py

Скрипт, которым получены числа в статье, и запись прогона. Файл читается на сборке из репозитория — это тот самый код, который запускали, а не его копия.

Цитируется в статье
/ru/interview/python/unit-testing
Как запустить
python3.11 bench/testing/basics.py
python3.12 bench/testing/basics.py
python3.13 bench/testing/basics.py
python3.14 bench/testing/basics.py

python3.13 -m venv .venv-pytest
.venv-pytest/bin/pip install pytest==8.4.2
.venv-pytest/bin/python bench/testing/pytest_run.py
.venv-pytest/bin/python bench/testing/checkout_run.py

python3.11 bench/testing/practice.py
python3.12 bench/testing/practice.py
python3.13 bench/testing/practice.py
python3.14 bench/testing/practice.py

Запись прогона

Замеры для урока «Тестирование»

Скрипт Что делает
basics.py наблюдения на стандартной библиотеке: FAIL против ERROR, Mock и его поля, опечатки в именах проверок, где патчить, Mock против create_autospec, порядок тестов и setUp, таблица Mock / spec / spec_set / autospec (раздел 7)
pytest_run.py запускает pytest_examples.py под pytest и печатает вывод без времени и путей: разложенный assert, parametrize, фикстура с yield, pytest.raises, исключение в теле теста (FAILED) и упавшая фикстура (ERROR)
pytest_examples.py примеры тестов pytest; четыре не проходят нарочно — три FAILED и один ERROR. Назван не test_*.py, чтобы pytest не подхватывал его сам
checkout.py, test_checkout.py полный зелёный пример урока: рабочий код и тесты к нему (результат — главное утверждение, вызов mock — дополнительное; parametrize, фикстура). Запуск — python -m pytest -v в каталоге с обоими файлами
checkout_run.py копирует оба файла в пустой каталог, запускает python -m pytest, затем портит копию тремя способами и запускает снова
practice.py источник ответов двух практических задач урока
python3.11 bench/testing/basics.py
python3.12 bench/testing/basics.py
python3.13 bench/testing/basics.py
python3.14 bench/testing/basics.py

python3.13 -m venv .venv-pytest
.venv-pytest/bin/pip install pytest==8.4.2
.venv-pytest/bin/python bench/testing/pytest_run.py
.venv-pytest/bin/python bench/testing/checkout_run.py

python3.11 bench/testing/practice.py
python3.12 bench/testing/practice.py
python3.13 bench/testing/practice.py
python3.14 bench/testing/practice.py

Записи в runs/: без суффикса — CPython 3.13.7, остальные — с суффиксом версии (3.11.15, 3.12.3, 3.14.7); pytest_run.txt и checkout_run.txt — CPython 3.13.7 и pytest 8.4.2; всё — linux/amd64. basics*.txt перезапущены 14.10.2026 после добавления раздела 7 — разделы 1–6 не изменились.

Что получилось

FAIL против ERROR: assertEqual(add(2, 2), 5) — FAIL, голый assert — тоже FAIL (AssertionError), add(2, "2") — ERROR с TypeError; итого тестов 4, failures 2, errors 1.

pytest: 9 тестов — 3 failed, 5 passed, 1 error. Исключение в теле теста (test_type_error) — FAILED, упавшая фикстура (test_with_broken_fixture) — ERROR; тесты идут в порядке файла.

Практика: Mock(return_value=10), вызванный как m(1) и m(2), — 10 10 2 и call(2); в TestCase с пятью тестами setUp вызван 5 раз.

Опечатки в проверках (mock ни разу не вызывали): asert_called_once и assret_called_once — AttributeError на всех версиях; called_once — на 3.11 проходит молча, с 3.12 — AttributeError; was_called_once — молча на всех версиях.

Где патчить: под patch('time.time') модуль с import time видит подмену, модуль с from time import time — нет; patch('app_from.time') подменяет.

Спецификация: голый Mock принял charge(100) без валюты и метод chrage; create_autospec ответил TypeError: missing a required argument: 'currency' и AttributeError.

Порядок: объявлены test_zebra, test_apple, test_mango — выполнены apple, mango, zebra; setUpClass — 1 раз, setUp — 3.

Остальные строки вывода одинаковы на 3.11–3.14.

Mock / spec / spec_set / autospec (раздел 7, добавлен 14.10.2026 по внешнему аудиту, замечание PY-TEST-01; одинаково на 3.11–3.14). Несуществующее имя m.chrage: голый Mock — принято, остальные три — AttributeError. Присваивание m.extra = 1: AttributeError только у spec_set (и у create_autospec(..., spec_set=True)). Вызов m.charge(100) без валюты: Mock(), Mock(spec=…) и Mock(spec_set=…) — принято, create_autospec — TypeError: missing a required argument: 'currency'. m.was_called_once(): голый Mock — принято, со спецификацией любого вида — AttributeError.

Полный пример (checkout_run.txt): как есть — 4 passed. Дефект «скидка прибавляется» — 1 failed (assert 130 == 70). Дефект «charge без валюты»: с фикстурой на create_autospec — 4 failed (TypeError), с фикстурой на Mock(spec=PaymentClient) — 1 failed, 3 passed: тесты, проверяющие только результат, дефект не видят, его ловит лишь assert_called_once_with.

Скрипт

34 строк
"""Запуск pytest_examples.py и запись вывода без времени и путей.

Нужен pytest. В записи прогона — CPython 3.13.7 и pytest 8.4.2, установленный
в отдельное окружение:

    python3.13 -m venv .venv-pytest
    .venv-pytest/bin/pip install pytest==8.4.2
    .venv-pytest/bin/python bench/testing/pytest_run.py

Что видно в выводе: как pytest раскладывает упавший assert, что
parametrize даёт отдельный тест на каждый набор данных, в каком порядке
работает фикстура с yield и что pytest.raises засчитывает исключение как успех.
"""
import os
import re
import subprocess
import sys

import pytest

here = os.path.dirname(os.path.abspath(__file__))
print("PY", sys.version.split()[0], "| pytest", pytest.__version__)
print()
out = subprocess.run(
    [sys.executable, "-m", "pytest", "pytest_examples.py", "-v", "-s", "-p", "no:cacheprovider", "--no-header", "-rN"],
    cwd=here,
    capture_output=True,
    text=True,
    env=dict(os.environ, PYTHONDONTWRITEBYTECODE="1"),
).stdout
out = re.sub(r" in \d+\.\d+s", " in <время>", out)
out = out.replace(here, "<bench>")
print(out.rstrip())