Баг, который закрылся сам
Консультант Aldervane списывает восемь часов на аудиторский проект Nordvale. Экран табеля показывает запись. Недельная сводка показывает восемь часов. Консультант доволен, тестировщик отмечает кейс как пройденный, а через три недели менеджер проекта сообщает, что Nordvale недовыставили счёт на восемь часов.
На экране не было ничего неверного. Строка в TimeEntries была записана с EngagementId, указывающим на закрытый проект, а задание выставления счетов пропускает закрытые проекты. Каждый слой над базой данных был согласован, и каждый слой над базой данных сообщал одно и то же неверное.

Описание
An isometric scene on a dark navy background. On the left, a frosted-glass monitor displays a glowing turquoise square with a white check mark; its stand is labelled "What the UI shows". A thin dashed line runs from the screen to the right, to a grey pedestal carrying one amber cube, labelled "What was written". The point: the screen reports a success, while the row behind it is the suspect one — here, a time entry attached to a closed engagement. The dashed line is the only connection the tester has between the two, and the only way to follow it is a query.
Именно для этой ситуации и существует курс. Интерфейс может сообщить вам только то, что думает интерфейс. Когда нужно знать, что система записала на самом деле, смотреть можно ровно в одно место.
Четыре момента
На протяжении цикла тестирования запрос — единственное доступное доказательство в четырёх точках.
До теста — подготовка состояния. Вам нужен проект в определённом состоянии: утверждённый, частично выставленный в счёт, с одной заявкой на возмещение расходов, всё ещё ожидающей рассмотрения. Прокликивание до этого состояния через UI занимает двадцать минут и не повторяемо. Скрипт подготовки занимает секунды и создаёт одно и то же состояние при каждом прогоне.
После действия — проверка записи. Приложение говорит, что сохранило. Вы проверяете, что оно сохранило: какую таблицу, какие столбцы, какие значения и — обычно самое интересное — какие столбцы оно оставило со значениями по умолчанию, когда не должно было.
Во время расследования — поиск формы дефекта. В баг-репорте сказано: «иногда появляются дублирующиеся строки счёта». Запрос к InvoiceLines с группировкой по проекту и описанию строки превращает «иногда» в «на 14 проектах, все созданы после мартовского релиза, у всех смена ставки в середине периода».
Во время сверки — доказательство, что ничего не потеряно. Миграция переносит три года записей времени в TimeEntries из StagingTimeEntries. Никто не может проверить 400 000 строк вручную. Количество строк, сумма часов и разность множеств — могут.
Описание
Stages left to right: Arrange state, Act through the UI or API, Verify the write, Investigate a defect; a dashed branch from Verify, labelled "many rows", leads to Reconcile a bulk operation. The Act stage has a dotted note: the interface reports what the interface believes. Four database cylinders feed evidence into four stages, in the accent colour: a setup script into Arrange; one known row with known values into Verify; find what the bad rows share into Investigate; counts, totals and set difference into Reconcile. Act is the only stage with no database cylinder of its own.
Четыре момента различаются тем, что вам уже известно. Подготовка: вы знаете цель и строите её. Проверка: вы точно знаете, какую строку и какие значения ожидать. Расследование: вы ещё не знаете, что общего у плохих строк. Сверка: вы вообще не можете проверить строки по отдельности, поэтому вместо этого сравниваете совокупности.
Сначала вопрос, потом запрос
Самая дорогая ошибка в этой дисциплине — не синтаксическая. Синтаксические ошибки заявляют о себе сами. Дорогая ошибка — это запрос, который выполняется без ошибок, возвращает правдоподобные строки и отвечает на вопрос, который никто не задавал.
Тестировщик пишет:
SELECT * FROM TimeEntries WHERE EngagementId = 4821;
Строки возвращаются. Тестировщик заключает, что время списано. Но проверочный вопрос был не «есть ли записи времени по этому проекту», а «сохранена ли только что отправленная мной запись с теми часами, датой и ставкой, которые я отправил, и привязана ли она к открытому проекту». Это разные вопросы, и задан был только один из них.
Поэтому привычка — с первого юнита: сформулируйте проверочный вопрос одним предложением, словами, до того как писать какой-либо SQL. Затем напишите запрос, который отвечает на это предложение и ни на что больше. Когда запрос вернулся и вы собираетесь объявить успех, перечитайте своё предложение и спросите себя, действительно ли результат его решает.
Полезное предложение состоит из трёх частей — какие строки, какие значения и что будет считаться провалом. «Ровно одна строка в TimeEntries для сотрудника 331 на 2026-03-14, с Hours = 8.0 и EngagementId, указывающим на проект, у которого Status — Open; ноль строк, больше одной строки или закрытый проект — провал».
Это предложение длиннее запроса, который из него получается. Это нормально, и в этом весь смысл.
«Только чтение» — по умолчанию
Вы работаете в среде, которой пользуются и другие люди. Коллега посреди регрессии на той же схеме. Всегда исходите из того, что ваша сессия не одна.
Позиция по умолчанию: читать. SELECT не может испортить никому вторую половину дня.
Три ситуации оправдывают выход из этого режима, и каждая несёт обязательство.
Подготовка состояния, которое нельзя подготовить через UI. Обязательство: знать, как это откатить, и откатить.
Воспроизведение дефекта, которому нужны данные в невозможном состоянии — строка счёта с NULL в ставке, табель, датированный раньше начала проекта. Обязательство: то же самое плюс записка тому, кто отвечает за среду.
Уборка после собственного прогона теста. Обязательство: затрагивать только то, что создали вы, по идентификатору, который вы контролируете.
Всё, что за пределами этих трёх, — работа кого-то другого. Если исправление требует UPDATE на данных, похожих на продуктивные, это запрос на изменение, а не шаг теста — и то, что у вас есть право его выполнить, не то же самое, что полномочия на это.
Механика безопасного выхода из режима по умолчанию — явные транзакции, выполнение SELECT перед DELETE, что ROLLBACK откатывает и чего не откатывает — это юнит 3. Пока вы его не прочли, оставайтесь на SELECT.
Прежде чем что-либо запускать: данные клиента не покидают среду
Каждый запрос в этом курсе написан к ENGAGE — вымышленной системе, принадлежащей вымышленной фирме. На реальном проекте строки, которые вы выбираете, конфиденциальны для клиента — гонорары, ставки сотрудников, невыставленное время, иногда собственные операционные данные клиента, поступившие в рамках аудиторского мандата.
Эти строки не попадают в ИИ-ассистент, публичный форматировщик SQL, pastebin, снимок экрана в групповом чате или комментарий к тикету. Ни для того, чтобы получить помощь с запросом. Ни потому, что инструмент обещает не обучаться на них.
Когда вам нужна помощь с запросом, безопасно передаются две вещи: запрос без результатов и задача, пересказанная на выдуманных значениях. Обеих почти всегда достаточно — трудность почти никогда не в данных. Юнит 22 разбирает более сложные случаи: что считается достаточно обезличенным, что прикладывать к дефект-репорту и где проходит граница с локально размещённой моделью.
Как выглядит ENGAGE
Как следует вы познакомитесь со схемой в юните 2. Пока — её общий вид:
Clients и Engagements наверху — у клиента много проектов, у проекта есть статус и диапазон дат. Staff — это люди; TimeEntries связывают сотрудника с проектом на дату, с часами и ставкой. Approvals фиксируют, кто что утвердил и когда. Invoices и InvoiceLines превращают утверждённое время и ExpenseClaims в деньги. AuditLog фиксирует изменения любого из этого. StagingTimeEntries — куда приземляется миграция из старой системы, прежде чем чему-либо в ней начнут доверять, а ArchivedTimeEntries хранит записи до 2024 года в той же форме, что и TimeEntries.
Одиннадцать таблиц, одна фирма, и каждый дефект в этом курсе живёт где-то внутри них.
Цена
SQL даёт тестировщику прямое доказательство — самое сильное из всех. Цена в том, что прямое доказательство не прощает неточных вопросов. Проверка через UI, задающая не тот вопрос, обычно выглядит на экране странно. Запрос, задающий не тот вопрос, возвращает аккуратный набор результатов и ложное ощущение, что что-то проверено.
Всё дальнейшее так или иначе — об этом разрыве.
