Баг-репорт: как написать, чтобы починили

Баг-репорт — это не жалоба, а инструкция: разработчик должен воспроизвести проблему за минуту, не задав тебе ни одного вопроса.

Баг-репорт — описание дефекта, по которому его можно воспроизвести, оценить и починить: шаги, фактический результат, ожидаемый результат, окружение.

Разработчик открывает твой баг между двумя задачами. У него в голове чужой код, и он тратит на чтение секунд тридцать. Если за эти тридцать секунд он не понял, что делать, — задача уедет в статус «Нужна информация», и починка сдвинется на день, а то и на спринт. Так что качество репорта — это буквально скорость починки. Тестировщика в команде оценивают не по количеству найденных багов, а по тому, сколько из них починили без переписки.

Анатомия баг-репорта

ПолеЧто туда пишем
ЗаголовокЧто сломалось, где и при каких условиях — одной строкой
ОкружениеСтенд, версия сборки, ОС, браузер, устройство, роль пользователя
ПредусловияСостояние системы до шагов: какой аккаунт, что в корзине, какие фича-флаги
Шаги воспроизведенияПронумерованный минимум действий от чистого состояния до бага
Фактический результатЧто происходит на самом деле — сухо, без интерпретаций
Ожидаемый результатЧто должно происходить и на каком основании (требование, макет, здравый смысл)
SeverityНасколько страшны последствия для системы
PriorityНасколько срочно чинить
ВложенияСкриншот со стрелкой, видео, лог, HAR-файл, запрос и ответ API, ID заказа
ВоспроизводимостьВсегда / 3 раза из 10 / однократно

Заголовок по формуле «что — где — когда»

Заголовок читают в списке из сорока багов, и по нему принимают решение, открывать ли задачу вообще. Работает простая формула: что происходит + где + при каком условии.

Плохой заголовокХороший заголовок
Не работает корзинаКорзина: итоговая сумма не пересчитывается при удалении товара (остаётся сумма до удаления)
Баг в оплате!!!Оплата: списываются деньги, но заказ не создаётся, если нажать «Оплатить» дважды подряд
Кривой поискПоиск: запрос из одной буквы «а» возвращает 500 Internal Server Error
Странное поведение на мобилеiOS Safari: клавиатура перекрывает поле «Промокод», поле не проскроллить

Шаги воспроизведения

Главное правило — минимальный набор. Ты нашёл баг после сорока минут блужданий по приложению, но в репорт идут только те шаги, без которых баг не повторится. Проверь это сам: пройди свои шаги ещё раз с нуля, на чистой сессии. Половина «плавающих» багов при такой перепроверке оказывается вполне стабильной, просто в первый раз ты не заметил, что был залогинен под админом.

Фактический и ожидаемый результат

Эти два поля новички любят схлопывать в одно: «не работает». Разница принципиальная. Фактический — то, что видит глаз: «на экране «Оплачено», заказ в списке не появился, в логе POST /orders 500». Ожидаемый — то, что должно быть, и желательно со ссылкой на источник: требование, макет, поведение соседнего экрана. Если ожидаемого результата нет нигде — это не баг, а вопрос к аналитику, и такой репорт закроют как «Not a bug» или «By design». Половина споров «баг или не баг» решается ровно тем, что тестировщик заранее нашёл, на что сослаться.

Severity против Priority

Два поля, которые вечно путают, потому что оба «про важность». На самом деле они про разное, и ставят их разные люди: severity — тестировщик (это про технику), priority — менеджер или владелец продукта (это про бизнес и сроки).

Severity (важность)Priority (срочность)
Насколько сильно дефект бьёт по работоспособностиВ каком порядке чинить
Ставит тестировщик, объективноСтавит менеджер, исходя из бизнеса и релизов
Blocker, Critical, Major, Minor, TrivialHigh, Medium, Low
Почти не меняется со временемМеняется постоянно: планы, релизы, реклама

Ключ в том, что они независимы — и все четыре комбинации встречаются в жизни.

КомбинацияПример
Severity высокий, Priority высокийОплата падает у всех пользователей. Магазин не зарабатывает — чинить сейчас
Severity высокий, Priority низкийПриложение падает при смене языка на суахили. Падение — это серьёзно, но суахили в продукте нет, язык добавят через год
Severity низкий, Priority высокийВ названии компании на главной опечатка: «Сбербанк» → «Сберьанк». Система работает идеально, но это лицо продукта, и в понедельник запускается реклама
Severity низкий, Priority низкийВ админке иконка на два пикселя левее макета. Когда-нибудь

Разбор: плохой репорт и хороший

Реальный текст из чата, только имена изменены.

Заголовок: Не работает скидка
Описание:  Захожу в корзину, применяю промокод, скидка не считается.
           Сделайте что-нибудь, у меня демо в четверг.
Severity:  Critical

Что здесь не так. Какой промокод — неизвестно, промокодов в системе двести. Что значит «не считается» — не применился вовсе, применился не к тому товару, применился, но сумма не изменилась? Какая корзина, какой стенд, какая роль? Ожидаемого результата нет — а вдруг промокод и не должен действовать на товары со скидкой, и это правило записано в требованиях? Severity Critical поставлен из-за демо в четверг, то есть перепутан со срочностью. Разработчик потратит на переписку минут двадцать, и хорошо если сегодня.

Тот же дефект, оформленный так, чтобы его починили с первого захода.

Заголовок: Корзина: промокод SUMMER20 не уменьшает итоговую сумму,
           если в корзине есть товар из категории «Уценка»

Окружение: stage, build 2.14.3, Chrome 126 / Windows 11
           Пользователь: buyer@test.io (роль customer)

Предусловия:
  1. Промокод SUMMER20 активен, скидка 20%, ограничений по категориям нет
     (см. требование REQ-338)
  2. Корзина пуста

Шаги:
  1. Добавить в корзину «Чайник Bosch» — 3000 ₽ (обычный товар)
  2. Добавить в корзину «Кружка» — 500 ₽ (категория «Уценка»)
  3. Открыть /cart
  4. Ввести в поле «Промокод» значение SUMMER20 и нажать «Применить»

Фактический результат:
  Появляется зелёная плашка «Промокод применён», строка «Скидка: −700 ₽»
  отображается, но «Итого» остаётся 3500 ₽.
  В ответе POST /api/cart/promo: {"discount": 700, "total": 3500}

Ожидаемый результат:
  «Итого» = 2800 ₽ (3500 − 700). По REQ-338 скидка применяется ко всей корзине.

Воспроизводимость: 5 из 5. Без товара из «Уценки» скидка считается верно
                   (2400 ₽ вместо 3000 ₽) — проблема только в этой комбинации.

Severity: Major (пользователь платит больше, чем должен)
Priority: ставит PO. Демо для клиента в четверг 17.07.

Вложения: screenshot-cart.png, har-file.har, order_id 10492

Разница не в объёме, а в том, что каждый абзац отвечает на вопрос, который иначе задали бы тебе. Отдельно отмечу строку про воспроизводимость: тестировщик не просто нашёл баг, он локализовал его — выяснил, что дело в товаре из «Уценки». Это половина работы разработчика, и именно за это тестировщика ценят.

Как это работает

После сохранения баг живёт своей жизнью в трекере (Jira, YouTrack, GitLab Issues), и у него есть жизненный цикл.

  1. New / Open — репорт создан.
  2. Triage — команда разбирает: подтверждает, назначает приоритет, вешает исполнителя. Здесь же баг могут закрыть как Duplicate (уже есть такой), Not a bug (система работает по требованиям), Cannot reproduce (не повторяется — обычно потому, что шаги неполные), Won't fix (знаем, чинить не будем).
  3. In progress → Fixed — разработчик починил и указал сборку, в которой правка появится.
  4. Verify — тестировщик берёт ту самую сборку и проходит свои же шаги. Прошло — Closed. Не прошло — Reopen, и это, между прочим, важный сигнал: много reopen означает, что чинили симптом, а не причину.

Отдельно про проверку починки: мало пройти шаги из бага. Нужно ещё проверить соседнее — вдруг починив скидку на «Уценку», сломали скидку на обычные товары. Такая проверка вокруг исправления называется регрессионной, и без неё поход по кругу «починили — сломали другое» никогда не закончится.

Частые ошибки

  • Скриншот вместо текста. По картинке нельзя искать, нельзя скопировать текст ошибки, и через год она превратится в битую ссылку. Скриншот — приложение к описанию, а не замена ему.
  • Несколько дефектов в одном репорте. «И ещё там кнопка съехала». Чинить их будут разные люди в разное время, а закрыть задачу можно только целиком.
  • Интерпретация вместо факта. «Падает валидация на бэкенде» — это гипотеза. Пиши, что видишь: код ответа, текст на экране, запись в логе. Гипотезу оставь отдельной строкой «Предположительно…».
  • Severity ставится по эмоции. Blocker — это когда дальше тестировать нельзя вообще, а не когда «мне очень надо».
  • Шаги от середины. Автор забыл, что был залогинен под админом с включённым фича-флагом. Разработчик воспроизвести не смог, баг закрыт как Cannot reproduce, а он живой и в проде.
  • Нет версии сборки. Через три дня баг чинят, а он уже был починен позавчера в другой ветке — или наоборот, репорт написан по старому стенду.
  • Оценочные суждения в тексте. «Кто вообще это писал?!» — снижает шанс на быструю починку сильнее, чем любой пропущенный шаг.

Итоги

  • Баг-репорт — инструкция по воспроизведению, а не сообщение о недовольстве. Цель — починка без единого уточняющего вопроса.
  • Заголовок по формуле «что — где — при каком условии»: по нему принимают решение открыть задачу.
  • Шаги — минимальные и от чистого состояния; фактический результат — факты, ожидаемый — со ссылкой на требование.
  • Severity — про последствия, ставит тестировщик. Priority — про срочность, ставит менеджер. Все четыре комбинации реальны.
  • Локализация («баг только при товаре из категории Уценка») превращает репорт из жалобы в половину решения.
  • Жизненный цикл: New → Triage → In progress → Fixed → Verify → Closed, с ветками Duplicate, Not a bug, Cannot reproduce, Won't fix и Reopen.
Проверьте себя
1. Приложение падает при переключении языка на суахили. Суахили в продукте нет, локализацию планируют через год. Какие severity и priority уместны?
ASeverity низкий, priority низкий: язык всё равно не используется
BSeverity высокий, priority низкий: падение серьёзное, но чинить не срочно
CSeverity высокий, priority высокий: падение всегда чинят немедленно
DSeverity и priority всегда совпадают, так что оба средние
2. Какая формулировка в баг-репорте нарушает правило «пиши факты, а не интерпретации»?
AНа экране появляется текст «Оплачено», заказ в списке не появился
BВ ответе POST /orders приходит 500 Internal Server Error
CВалидация на бэкенде отваливается из-за неправильного парсинга даты
DВоспроизводится 5 раз из 5 на сборке 2.14.3