Пирамида была, середина пустая
Главы о тестировании у Ричардсона мы читали, ожидая приговора «у вас ничего нет», и получили другое. Пирамида на месте, просто выглядит непривычно: проверки отдельных страниц — нижний слой, ритуал приёмки задачи по источнику истины — средний, состязательная перепроверка вывода второй моделью — верхний.
Провал обнаружился в одном месте, зато самом дорогом. Серверная часть, через которую идут заявки, счета и правки базы, не покрывалась ничем: ни одного автоматического сценария, проверка синтаксиса перед коммитом — и всё. Дефект виден по последствиям: один запрос уже обнулял базу заявок, и обнаружил это человек.
Как тестировать то, что нельзя останавливать
Решение оказалось дешевле страха. Тест поднимает настоящий локальный сервер на временном зеркале серверной части, авторизация идёт штатным механизмом, а не заглушкой, боевые данные не участвуют вовсе. Полный прогон занимает секунды и встаёт в тот же гейт, где раньше стояла одна проверка синтаксиса. Проверяются не «функции», а те способы сломать систему, которые уже случались или очевидно случатся.
| Что проверяет тест | Какой инцидент за этим стоит |
|---|---|
| Отказ без входа | доступ к данным мимо авторизации |
| Конфликт версий при одновременной правке | две сессии пишут разом и затирают друг друга |
| Запрет обнуления: пустое поверх непустого не проходит | запрос, обнуливший базу заявок |
| Частичное сохранение не теряет соседние разделы | правка одного раздела уносила остальные |
| Мусор на входе не даёт внутренней ошибки | невалидное тело запроса роняло обработчик |
| Первичное заполнение из образца | пустая база после переустановки |
| После работы не остаётся временных файлов | мусор от прерванной записи копился рядом |
| Резервная копия на месте | откатываться было не к чему |
| Итог прогона | 15 из 15 зелёных |
Соседний пункт того же дня закрывался по такому же принципу. Восемь оставшихся точек записи перевели на общий помощник атомарной записи, и подтверждением служил не факт правки, а два прогона: синтаксическая проверка по всем восьми файлам и функциональный тест помощника — испорченный на входе текст не должен затирать существующий реестр.
Свойства вместо примеров
Второй закрытый пункт интереснее первого, потому что тестируется там не сценарий, а математика. Инструмент слияния сводит правки нескольких сессий, и лекция про типы данных, устойчивые к слиянию, задаёт функции три обязательных свойства: результат не зависит от порядка аргументов, не зависит от группировки и не меняется при повторном применении.
Ценность формулировки в том, что все три проверяются напрямую, без придумывания примеров: слияние двух копий в одном порядке равно слиянию в обратном, слияние копии с самой собой возвращает её же. Дальше добавились свойства из предметной области.
- Без потерь — ни одна задача из обеих копий не исчезает.
- Идемпотентность — повторное слияние ничего не меняет.
- Симметрия — «своя» и «чужая» копия равноправны.
- Удаления не применяются — стирание в одной копии не выносит запись из результата.
- Без дублей — один идентификатор остаётся одной записью.
- Политика победителя — при равенстве прочего выигрывает оговорённая сторона.
Предварительным условием стал рефакторинг: логику слияния вынесли из скрипта в чистую функцию — без чтения файлов, без записи, без побочных эффектов. Её можно вызвать семь раз с разными аргументами, а скрипт, который сам ищет файлы и сам их перезаписывает, пришлось бы проверять запуском на живых данных — потому его обычно не проверяют вовсе. Итог — 7 тестов, все зелёные.
Правило, которое это обобщает
Формулировка из справочника паттернов короче любого нашего регламента: если утверждение об инварианте нельзя выразить средствами языка, пишите на него автоматический тест. Смысл не в тестах как таковых, а в переводе договорённостей из разряда «все же понимают» в разряд проверяемого.
Договорённостей такого рода накопилось много, и почти все живут в тексте. Сумма счёта равна сумме его строк. У рейса есть машина. Класс тарифа соответствует машине, а не весу груза. Ни одно из трёх утверждений нельзя выразить структурой файла, где всё это просто поля, — значит, по правилу Эванса каждое обязано существовать как проверка.
Чего у нас всё ещё нет
- Автопроверки общего ядра на каждом шаге. Справочник требует для общей части модели постоянной интеграции — частого сведения и автотестов, ловящих расхождение. У нас проверка включается при конфликте слияния: расхождение обнаруживается позже, чем возникло.
- Полного пакета инвариантов. Три утверждения выше проверяются частично, сплошного набора нет.
- Схемы данных. Единственная незакрытая дыра из семи по итогам разбора; описание структуры, версия формата и проверка при записи разобраны отдельным материалом.
Мера, по которой мы теперь считаем дыру закрытой. Не «код написан» и не «правило записано», а «есть проверка, которая краснеет, если это сломать, и она прогнана». По такой мере из семи дыр разбора закрыто пять, одна в работе и одна открыта — и последняя строка нам нравится больше, чем круглый отчёт.