← назад к разделу

Покупатель пишет в поддержку: промокод не сработал. Вы открываете форму заказа и проверяете по одному полю: промокод применяется, скидка постоянного покупателя начисляется, оплата при получении проходит. Каждое поле исправно — а баг живёт в сочетании: промокод пропадает только у постоянного покупателя, выбравшего оплату при получении.

Классы эквивалентности и границы на такое не отвечают — они про одно поле. Отвечают два приёма: таблица решений раскладывает сочетания, когда у каждого свой результат, а попарное тестирование сжимает список, когда сочетаний десятки.

строка — способ оплаты, столбец — промокод × статус × доставка 48 клеток — все сочетания: 2 × 3 × 4 × 2 без промокода с промокодом карта бонусы при получении рассрочка 12 строк — в них есть каждая пара значений тройка «новый + рассрочка + курьер» не проверена все пары покрыты тройки — только половина из 24

Сорок восемь клеток — все сочетания четырёх условий формы заказа. Подсвеченные двенадцать подобраны так, что любая пара значений встречается хотя бы в одной из них: по три клетки в каждой строке и ровно по одной в каждом столбце. Цена сжатия видна тут же: сочетания из трёх условий покрыты наполовину, и обведённая клетка — одно из непроверенных.

Сочетание — это отдельный объект проверки

Кажется, что если каждое поле проверено, то проверена и форма. Именно на этом баг из начала статьи и держится: за формой стоит не одно сравнение на поле, а цепочка вложенных условий — «если введён промокод, скидка 10%; иначе если покупатель постоянный, 5%; если оплата при получении, скидок нет». Двигая поля по очереди, вы идёте по верхним веткам, а ветка, срабатывающая только на сочетании, не выполняется ни разу: её нечем включить. Вдобавок ветки писали разные люди и в разное время — июньский запрет встал в начало цепочки, и мартовская скидка ниже перестала до неё доходить.

Отсюда выбор приёма: если у каждого сочетания свой результат (сумма скидки, доступное действие), важен каждый столбец — это территория таблицы решений; если условия всего лишь фон, а результат везде один, это территория попарного тестирования.

Таблица решений: пять шагов от требования к проверкам

Требование написано прозой, а проза не перечисляет случаи: она выглядит исчерпывающей ровно до момента, когда её пробуют применить к конкретному заказу.

Промокод даёт скидку 10%. Постоянным покупателям — 5%. При оплате при получении скидки не действуют.

Шагов пять. Выписать условия — вопросы к заказу, от ответа на которые зависит результат: введён ли промокод, постоянный ли покупатель, картой или при получении. Выписать варианты каждого: здесь у всех по два, «да» и «нет»; это те же классы эквивалентности, применённые к условию целиком. Перемножить: 2 × 2 × 2 = 8 строк, и пока список помещается на экран, его выписывают целиком — пропущенное видно именно в выписанном виде. Заполнить действия, беря их из требования, а не из работающей программы; чего в требовании нет, оставляют пустым. Свернуть повторы — про это следующий раздел.

Промокод введёнПостоянный покупательОплата картойСкидка
дадада?
даданет0%
данетда10%
данетнет0%
нетдада5%
нетданет0%
нетнетда0%
нетнетнет0%

Свёртка и пустая клетка в требованиях

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

Самое ценное в таблице — не готовые проверки, а клетка, которую нечем заполнить. У нас это первая строка: промокод введён, покупатель постоянный, оплата картой. Требование говорит и про 10%, и про 5%, но молчит, что делать, когда верно и то и другое. Ответов минимум четыре, и каждый где-то реализован: скидки складываются (15%), берётся большая (10%), приоритет у промокода, приоритет у статуса (5%). Разработчик выберет молча тот, который первым пришёл в голову, — поэтому знак вопроса не повод угадать, а находка для вопросов к требованиям: ошибка, найденная в тексте, стоит дешевле всех остальных. Дыры бывают трёх видов: требование молчит — нужен ответ владельца продукта; описано дважды и по-разному — противоречие; сочетания не должно быть вовсе (рассрочка при самовывозе) — его запрещают в интерфейсе.

Откуда берётся сорок восемь

Пока условий три, всё считается в уме. Реальная форма заказа сложнее: промокод — 2 варианта, статус покупателя (новый, обычный, постоянный) — 3, способ оплаты (карта, бонусы, при получении, рассрочка) — 4, доставка (курьер или самовывоз) — 2. Всех сочетаний 2 × 3 × 4 × 2 = 48: условий стало четыре вместо трёх, а сочетаний сорок восемь вместо восьми — каждое новое условие не прибавляет случаи, а умножает их. Галочка «подарочная упаковка» — уже 96, выбор из трёх городов — 288. Если одна проверка оформления занимает минут шесть, сорок восемь — почти пять часов на одну форму. И большинство этих сочетаний пройдут по одним и тем же веткам кода, отличаясь только цифрами.

Попарное покрытие: из сорока восьми в двенадцать

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

Критерий подсказывает статистика дефектов: заметная часть багов срабатывает от одного значения, вместе с парами набирается основная масса, а дефектов, которым нужно совпадение пяти-шести условий, почти не находится — ошибка выживает там, где встретились две ветки, порядок между которыми никто не согласовал. Отсюда критерий: набор достаточен, если каждая пара значений любых двух условий встречается в нём хотя бы раз.

Нижняя граница считается за секунду: возьмите два самых широких условия, статус покупателя (3 варианта) и способ оплаты (4). Их пар 3 × 4 = 12, каждая обязана встретиться, а в одну строку помещается одна такая пара — значит, строк не может быть меньше двенадцати.

№ПромокодСтатусОплатаДоставка
1нетновыйкартакурьер
2естьновыйбонусыкурьер
3нетновыйпри получениисамовывоз
4естьновыйрассрочкасамовывоз
5естьобычныйкартасамовывоз
6нетобычныйбонусысамовывоз
7естьобычныйпри получениикурьер
8нетобычныйрассрочкакурьер
9нетпостоянныйкартакурьер
10естьпостоянныйбонусыкурьер
11нетпостоянныйпри получениисамовывоз
12естьпостоянныйрассрочкасамовывоз

Набор проверяется вручную: «есть промокод + самовывоз» — строка 4, «постоянный + при получении» — строка 11. Собирают такие наборы генераторы, самый известный — консольный PICT, и одна их возможность обязательна: запреты вроде «рассрочка недоступна при самовывозе». Без них в набор попадут строки, которые физически нельзя ввести в форму; вы вычеркнете их на ходу и потеряете вместе с ними пары. И последнее: попарный набор — это список входных данных, а не тест-кейсов, ожидаемый результат к каждой строке пишут по той же таблице решений.

Чего попарное покрытие не ловит и когда перебирают всё

У сжатия есть цена: двенадцать строк покрывают все пары — и только пары. Сочетаний из трёх условий по осям «статус + оплата + доставка» ровно 3 × 4 × 2 = 24, а в двенадцати строках их помещается максимум двенадцать: половина троек не проверена ни разу. «Новый покупатель + рассрочка + курьер» в таблице не встречается, и если ошибка сидит именно там, все двенадцать проверок пройдут зелёными.

Поступают с этим тремя способами: дописывают руками три-четыре рискованные строки — где недавно чинили баг, на что жаловался покупатель; поднимают силу покрытия до троек, но нижняя граница тогда 3 × 4 × 2 = 24, вдвое дороже, поэтому так делают для одного узла; или делят форму — расчёт скидки перебирают таблицей решений целиком, а фон вокруг закрывают попарно.

Полный перебор возвращается там, где непроверенное сочетание дороже сэкономленных часов: деньги — скидки, налоги, бонусы, возвраты, где тройка означает неправильную сумму в чеке; права доступа — роль, владелец объекта, состояние, действие, где это чужой заказ у постороннего. И попарное не берут, когда условий мало: три по два варианта дают восемь проверок и пять после свёртки — поход к генератору того не стоит. Рабочее правило: у каждого сочетания свой результат — таблица решений, и столбцы проверяют все; результат везде один и тот же, а условий много — попарное покрытие.

Коротко

  • Проверка полей по одному не находит баг сочетания: ветка, срабатывающая только на нём, не выполняется ни разу.
  • Таблица решений раскладывает требование на случаи: условия → варианты → перемножить → действие → свернуть повторы.
  • Число сочетаний — произведение количеств вариантов, а не сумма: 2 × 3 × 4 × 2 = 48.
  • Клетка, которую нечем заполнить, — главная находка таблицы: сочетание уточняют, а не угадывают.
  • Попарное покрытие требует каждую пару значений двух любых условий хотя бы раз; меньше двенадцати строк не бывает — это произведение двух самых широких условий, 3 × 4.
  • Цена сжатия — тройки: из 24 в набор попадает половина, поэтому деньги и права доступа перебирают полностью.

Что почитать дальше