Какие проверки отдать машине и как выглядит набор автотестов, который реально помогает
Автотест — программа, которая выполняет проверку и сама сравнивает фактический результат с ожидаемым. Ручной тест-кейс говорит человеку: «откройте, введите, сравните». Автотест делает то же самое кодом и в конце отвечает одним словом: прошёл или упал.
| Ручная проверка | Автотест | |
|---|---|---|
| Кто сравнивает | Человек, глазами | Программа, строкой assert |
| Повтор | Каждый раз заново, минуты и часы | Одной командой, секунды |
| Замечает неожиданное | Да: «а почему кнопка серая?» | Нет: проверяет только то, что написано |
| Стоимость | Дёшево один раз, дорого сто раз | Дорого написать и поддерживать, дёшево повторять |
| Устаёт и пропускает | Да, особенно на десятом повторе | Нет, но «слепнет», если ожидание записано неверно |
Автоматизация не заменяет тестировщика. Она забирает у него повторяющуюся работу — регресс, проверку десятков вариантов данных, проверку API после каждого изменения, — чтобы время осталось на исследовательское тестирование и на то, что машина не заметит.
Автотест окупается, когда суммарное время ручных повторов больше, чем время на его написание и поддержку. Грубая оценка: проверку, которую повторяют реже трёх-четырёх раз, автоматизировать обычно невыгодно.
| Автоматизировать в первую очередь | Оставить ручным |
|---|---|
| Регресс: проверки, которые повторяют перед каждым выпуском | Разовая проверка новой функции, которая ещё меняется каждый день |
| Много вариантов данных: границы, классы, комбинации | Удобство, внешний вид, «понятно ли пользователю» |
| API: быстро, стабильно, не зависит от вёрстки | Исследовательское тестирование |
| Расчёты, правила, проверки полей — модульными тестами | Проверки, для которых нужен живой человек: печать, звонок, бумажный документ |
| Smoke новой сборки: «главное вообще работает?» | Редкие сценарии, где тест дороже, чем вероятный ущерб |
Пример расчёта. Регресс «Задачника» вручную — 2 часа. Выпуски — раз в неделю. Набор автотестов на те же проверки пишется за 16 часов и требует около 30 минут поддержки в неделю. Ручной регресс за 10 недель — 20 часов, автоматизированный — 16 + 5 = 21 час. Окупаемость — примерно на одиннадцатой неделе; дальше каждая неделя экономит полтора часа и даёт прогон за минуту вместо двух часов.
Пирамида тестов — правило о пропорциях набора: внизу много быстрых и дешёвых тестов, наверху — немного медленных и дорогих.
| Уровень | Что проверяет | Скорость одного теста | Доля в наборе | Раздел курса |
|---|---|---|---|---|
| UI (сквозные) | Сценарий пользователя в браузере: вход, создание задачи, фильтр | Секунды | Немного: главные сценарии | 08–09 |
| API (интеграционные) | Запросы к серверу и ответы: коды, тело, права доступа | Десятки миллисекунд | Средняя часть | 06–07 |
| Модульные | Одна функция в изоляции: проверка поля, расчёт срока | Миллисекунды | Основа: большинство | 03–05 |
Почему не наоборот. UI-тест проходит через всё приложение сразу: браузер, вёрстку, сервер, базу. Он медленный, а когда падает, непонятно, где причина. Модульный тест падает ровно на той функции, которая сломана. Если проверку можно сделать уровнем ниже — её делают ниже. Проверку «название из 101 символа отклоняется» не нужно гонять через браузер: её проверяет модульный тест функции и один API-тест.
Перевёрнутая пирамида («рожок мороженого») — много UI-тестов и почти нет модульных — типичная ошибка команд, которые начинают автоматизацию с записи сценариев в браузере. Такой набор идёт час, падает от каждой правки вёрстки, и его перестают запускать.
Набор автотестов собирают из нескольких инструментов, у каждого своя задача. В курсе — Python и его библиотеки; у других языков те же роли, другие названия.
| Роль | В курсе | Аналоги |
|---|---|---|
| Тестовый фреймворк: находит тесты, запускает, пишет отчёт | pytest | JUnit (Java), Jest (JavaScript), xUnit (C#) |
| HTTP-клиент для API-тестов | requests | REST Assured, axios, HttpClient; Bruno и Postman — без кода |
| Управление браузером | Playwright | Selenium WebDriver, Cypress |
| Проверка структуры JSON | jsonschema | Ajv, everit-json-schema |
| Отчёты и покрытие | pytest-html, JUnit XML, coverage | Allure, JaCoCo, Istanbul |
| Непрерывная интеграция: запуск на каждый коммит | GitLab CI | GitFlic CI, GitHub Actions, Jenkins |
API- и UI-тесты проверяют программу снаружи, по сети и через браузер, поэтому годятся для проекта на любом языке: ваш итоговый проект может быть на PHP или C#, а тесты к нему — на Python. Модульные тесты пишут на языке самой программы.
Для курса подготовлен стартовый проект — небольшой набор автотестов «Задачника». Скачайте архив qa-starter.zip и распакуйте в отдельную папку — в нём уже есть tasker.py. Состав:
$ find . -type f -not -path "*/__pycache__/*" | sort
./client.py
./conftest.py
./pytest.ini
./README.md
./requirements.txt
./tasker.py
./tests/test_smoke.py
| Файл | Зачем |
|---|---|
tests/test_smoke.py | Сами тесты: пять проверок главного — страница входа, вход по API, свои задачи, создание и удаление, вход в браузере |
conftest.py | Общая подготовка для всех тестов: запуск «Задачника» на свободном порту со свежими данными, учётные записи, клиенты API (раздел 04) |
client.py | Короткие вызовы API: api.get("/api/tasks") вместо полного адреса и заголовка с токеном (раздел 06) |
pytest.ini | Настройки pytest: где искать тесты, маркеры smoke и slow |
requirements.txt | Закреплённые версии пакетов — у всех в группе одинаковые |
README.md | Как установить и запустить — коротко, для того, кто открыл проект впервые |
tasker.py | Проверяемое приложение; тесты запускают его сами, вручную запускать не нужно |
Важное свойство набора: каждый прогон начинается с чистых учебных данных и не трогает вашу копию tasker.db. Тесты, которые создают задачи, работают под новым пользователем, созданным для этого теста. Поэтому тесты можно запускать в любом порядке и сколько угодно раз — результат один и тот же.
Установка — один раз, в папке проекта. В Windows (PowerShell или командная строка):
py -m venv .venv
.venv\Scripts\activate
pip install -r requirements.txt
playwright install chromium
В Linux — то же с python3 -m venv .venv и source .venv/bin/activate. Последняя команда скачивает браузер Chromium для UI-тестов — сотни мегабайт, это займёт несколько минут; браузер ставится один раз на компьютер и подходит всем проектам с той же версией Playwright. Что делает каждая команда, разберём в разделе 02; если PowerShell не даёт выполнить activate, запустите один раз Set-ExecutionPolicy -Scope CurrentUser RemoteSigned.
Запуск — команда pytest в папке проекта:
$ pytest
============================= test session starts ==============================
platform linux -- Python 3.13.5, pytest-9.2.0, pluggy-1.7.0
rootdir: ~/qa/starter
configfile: pytest.ini
testpaths: tests
plugins: metadata-3.1.1, playwright-0.10.0, locust-2.47.0, html-4.2.0
collected 5 items
tests/test_smoke.py ..... [100%]
============================== 5 passed in 1.87s ===============================
Каждая точка — прошедший тест, в квадратных скобках — сколько процентов набора выполнено. Последняя строка — итог: сколько прошло, сколько упало и за какое время. Ключ -v выводит каждый тест отдельной строкой:
$ pytest -v
============================= test session starts ==============================
platform linux -- Python 3.13.5, pytest-9.2.0, pluggy-1.7.0 -- ~/qa/.venv/bin/python3
cachedir: .pytest_cache
metadata: {'Python': '3.13.5', 'Platform': 'Linux-6.12.95+deb13-amd64-x86_64-with-glibc2.41', 'Packages': {'pytest': '9.2.0', 'pluggy': '1.7.0'}, 'Plugins': {'metadata': '3.1.1', 'playwright': '0.10.0', 'locust': '2.47.0', 'html': '4.2.0'}}
rootdir: ~/qa/starter
configfile: pytest.ini
testpaths: tests
plugins: metadata-3.1.1, playwright-0.10.0, locust-2.47.0, html-4.2.0
collecting ... collected 5 items
tests/test_smoke.py::test_login_page_opens PASSED [ 20%]
tests/test_smoke.py::test_api_login_gives_token PASSED [ 40%]
tests/test_smoke.py::test_ivan_sees_own_tasks PASSED [ 60%]
tests/test_smoke.py::test_create_and_delete PASSED [ 80%]
tests/test_smoke.py::test_ui_login[chromium] PASSED [100%]
============================== 5 passed in 1.58s ===============================
У теста в браузере в имени добавился [chromium] — так pytest-playwright отмечает браузер, в котором шёл тест. Маркер slow позволяет пропустить медленные тесты, когда нужен быстрый ответ:
$ pytest -m "not slow"
============================= test session starts ==============================
platform linux -- Python 3.13.5, pytest-9.2.0, pluggy-1.7.0
rootdir: ~/qa/starter
configfile: pytest.ini
testpaths: tests
plugins: metadata-3.1.1, playwright-0.10.0, locust-2.47.0, html-4.2.0
collected 5 items / 1 deselected / 4 selected
tests/test_smoke.py .... [100%]
======================= 4 passed, 1 deselected in 0.27s ========================
Чтобы увидеть упавший тест, испортим ожидание: пусть тест считает, что у ivan 9 задач, а не 8.
$ pytest -m "not slow"
============================= test session starts ==============================
platform linux -- Python 3.13.5, pytest-9.2.0, pluggy-1.7.0
rootdir: ~/qa/starter
configfile: pytest.ini
testpaths: tests
plugins: metadata-3.1.1, playwright-0.10.0, locust-2.47.0, html-4.2.0
collected 5 items / 1 deselected / 4 selected
tests/test_smoke.py ..F. [100%]
=================================== FAILURES ===================================
___________________________ test_ivan_sees_own_tasks ___________________________
ivan = <client.Api object at 0x7f6b540b3890>
def test_ivan_sees_own_tasks(ivan):
tasks = ivan.get("/api/tasks").json()
> assert len(tasks) == 9
E AssertionError: assert 8 == 9
E + where 8 = len([{'id': 4, 'title': 'Отчёт по продажам за сентябрь', 'due': '2026-10-26', 'priority': 2, ...}, {'id': 1, 'title': 'Под...', 'priority': 1, ...}, {'id': 8, 'title': 'Заказать пропуск для гостя', 'due': '2026-11-02', 'priority': 3, ...}, ...])
tests/test_smoke.py:21: AssertionError
=========================== short test summary info ============================
FAILED tests/test_smoke.py::test_ivan_sees_own_tasks - AssertionError: assert...
================== 1 failed, 3 passed, 1 deselected in 0.32s ===================
Отчёт о падении читают сверху вниз:
F в строке прогресса — какой по счёту тест упал.FAILURES — имя теста и его код; строка со стрелкой > — проверка, которая не выполнилась.E — что было на самом деле: pytest сам показал, что len(tasks) равно 8, а ожидалось 9.short test summary info — краткий список всех упавших: удобно, когда их много.Упавший автотест — не баг-репорт. Сначала нужно понять, кто неправ: программа, тест или окружение. Здесь неправ тест: ожидание 9 ничем не обосновано. Если бы тест опирался на требование, а программа его нарушала, — это дефект, и тогда оформляют баг-репорт по правилам курса ручного тестирования, а в отчёт прикладывают вывод теста.
FAILURES и сначала решают, кто неправ — программа, тест или окружение