Любому QA известен такой метод минимизации тест-кейсов, как Pairwise Testing — попарное тестирование. Метод отличный, достаточно простой и проверенный множеством команд. Но что делать, если после его применения кейсов остается слишком много?
Именно так произошло в моем проекте, и сегодня я расскажу, как можно еще сильнее сократить количество тест-кейсов, не теряя при этом в качестве.
Для начала расскажу немного о продукте. В Тинькофф наша команда разрабатывала блоки — это React-компоненты, состоящие из реализации и конфигурации. Реализация — это непосредственно сам компонент, который мы разработали и который видит пользователь в браузере. Конфигурация — это JSON, который задает параметры и наполнение этого объекта.
Главная задача блоков — быть красивыми и одинаково отображаться у разных пользователей. При этом от конфигурации и контента блок может очень значительно меняться.
Например, блок может быть таким — без фона, с кнопкой и картинкой справа:
Или вот таким — с фоном, без кнопки и с картинкой слева:
Или вообще вот таким — со ссылкой вместо кнопки и без списка в тексте:
Все примеры выше — это один и тот же блок, который имеет одну версию конфигурации (структура JSON, которую умеет обрабатывать конкретно этот React-компонент), но разное ее наполнение.
Сама схема:
При этом у блока с картинкой справа будет значение
В этой статье я не буду касаться вопросов версионирования схемы блоков, правил наполнения контентом или того, откуда в блок приходят данные. Все это — отдельные темы, которые никак не влияют на составление набора тест-кейсов. Главное, знать: у нас есть много параметров, влияющих на наш компонент, и каждый из них может принимать свой набор значений.
Для примера выше я выбрала блок с достаточно простой конфигурацией. Но даже в нем проверка всех сочетаний значений всех параметров займет непозволительно много времени, особенно если придется учитывать кроссбраузерность. Обычно здесь на помощь приходит Pairwise Testing, или попарное тестирование. Про него уже написаны тонны статей и есть даже обучения. Если вдруг не сталкивались — обязательно почитайте.
Давайте прикинем, сколько тест-кейсов у нас получится при его применении. Мы имеем больше 25 параметров, и некоторые из них принимают аж 7 и 9 вариантов значений. Да, можно чем-то пренебречь: например, если вы проверяете верстку, guid вам не важен. Но при применении Pairwise Testing все равно получится больше 80 тест-кейсов. И это, как я уже писала, для не самого сложного блока и без учета кроссбраузерности. Блоков у нас сейчас больше 150, и их количество растет, так что позволить себе столько кейсов мы не можем, если хотим сохранить скорость тестирования и выпуска новых версий.
Метод попарного тестирования основан на утверждении о том, что большинство дефектов вызвано взаимодействием не более двух факторов. То есть большинство багов проявляются либо на одном значении какого-то параметра, либо на сочетании значений двух параметров. Мы решили пренебречь второй частью этого утверждения и предположили, что при проверке одного параметра все равно будет найдено большинство багов.
Тогда получается, что для тестирования нам нужно проверить каждое значение каждого параметра хотя бы один раз. Но при этом каждый блок несет в себе всю конфигурацию. Тогда можно в каждом новом кейсе проверять максимум еще не проверенных значений, чтобы минимизировать количество кейсов.
Разберем алгоритм построения кейсов на упрощенном примере. Возьмем из нашей схемы компонент button и составим для него тест-кейсы:
Для упрощения примера я сократила длину списка в
Здесь все как в попарном тестировании: надо понять, какие значения может принять каждое из полей. Например, у
Здесь, на мой взгляд, важно очень четко определить границы и функциональность своей системы и не проверять лишнее. То есть если проверка на обязательность ключей или валидация значения реализованы в сторонней системе, то и проверять эту функциональность нужно в сторонней системе. А мы должны в качестве кейсов использовать только «правильные» данные.
По большому счету этот же принцип используется в пирамиде тестирования. При желании самые критичные интеграционные тесты можно добавить к нам — например, проверять обработку не пришедшего ключа. Но таких тестов должно быть минимальное количество. Иной подход — стремление к исчерпывающему тестированию, которое, как всем известно, невозможно.
Итак, мы определили варианты контента для каждого поля и составили такую таблицу:
В эту таблицу входит каждый класс эквивалентности каждого параметра, но только один раз.
Вот такие классы значений в нашем случае классы:
Поскольку каждый блок несет в себе полную конфигурацию, нам нужно добавить какие-нибудь релевантные данные в пустые ячейки:
Теперь каждый столбец — это один кейс.
При этом, поскольку недостающие данные мы выбираем сами, мы можем генерировать кейсы исходя из приоритета. Например, если мы знаем, что в кнопке короткий текст используется чаще, чем текст средней длины, то и проверять его стоит чаще.
В примере выше также можно обратить внимание на «выпавшие» кейсы — кейсы, в которых какой-то параметр вообще не проверяется, хотя и присутствует в таблице. В данном случае
Чтобы «выпавшие» кейсы не привели к багам, раньше мы анализировали полученные наборы значений. Анализ происходил один раз при генерации тестовых наборов, и все «выпавшие» кейсы добавлялись вручную в итоговый тестовый набор. Такое решение проблемы достаточно топорное, но дешевое (если, конечно, у вас редко меняются конфигурации тестируемых объектов).
Более универсальное решение — разделить все значения на две группы:
Каждое небезопасное значение проверяется в своем тест-кейсе, обогатить кейс можно любыми безопасными данными. Для безопасных значений составляется таблица по инструкции выше.
Теперь осталось только сгенерировать конкретные значения вместо классов эквивалентности.
Здесь каждому проекту придется подобрать свои варианты значений, опираясь на особенности тестируемого объекта. Некоторые значения генерировать очень просто. Например, цвет для большинства полей можно просто брать любой. Для некоторых блоков при проверке цвета приходится добавлять градиент, но он вынесен в отдельный класс эквивалентности.
С текстом чуть сложнее: если сгенерировать строку из случайных символов, то не будут протестированы переносы, списки, теги, неразрывные пробелы. Мы генерируем короткие и средней длины строки из реального текста, обрезая его до нужного количества символов. А в длинном тексте проверяем:
Этот набор кейсов вытекает непосредственно из нашей реализации блоков. Например, все html-теги подключаются вместе, поэтому и смысла тестировать каждый нет. При этом ссылка и список проверяются отдельно, потому что у них отдельная визуальная обработка (подкрашивание при наведении и буллиты).
Получается, что для каждого проекта нужно составить свой актуальный набор контента исходя из реализации тестируемого объекта.
Конечно, на первый взгляд может показаться, что алгоритм сложный и не стоит потраченных усилий. Но если опустить все детали и исключения, которые я постаралась описать в каждом пункте выше, получается достаточно просто.
Шаг 1. Добавляем в таблицу параметров все возможные значения:
Шаг 2. Дублируем значения в пустые ячейки:
Шаг 3. Превращаем абстрактные значения в конкретные и получаем кейсы:
Каждый столбец таблицы — это один кейс.
Такой способ генерации тест-кейсов имеет несколько важных преимуществ.
Первое и очевидное — кейсов значительно меньше, чем в попарном тестировании. Если брать упрощенный пример с кнопкой — у нас получилось 4 кейса вместо 8 в попарном тестировании.
Чем больше параметров в тестируемом объекте, тем значительнее будет экономия кейсов. Например, для полного блока, представленного в начале статьи, у нас получается 11 кейсов, а с помощью попарного — 260.
Второй плюс — при появлении новых параметров, учитывающихся при тестировании, количество кейсов увеличивается далеко не всегда.
Например, пусть в нашу кнопку добавится параметр
Количество кейсов увеличится только в том случае, если у какого-то параметра станет больше значений, чем было кейсов.
За счет обогащения значениями (шаг 2 в алгоритме) можно чаще проверять более приоритетные или более рискованные значения.
Например, если мы знаем, что раньше пользователи чаще использовали короткий текст, а теперь — более длинный, мы можем обогащать кейсы более длинным текстом и чаще попадать в реальные пользовательские кейсы.
Алгоритм, приведенный выше, вполне поддается автоматизации. Конечно, сгенерированные алгоритмом кейсы будут меньше походить на реальные, чем сгенерированные человеком. Хотя бы за счет подбора цветов и обрезания текста.
Но зато уже в процессе разработки без участия тестировщика появляются кейсы, что очень сильно сокращает петлю обратной связи.
Естественно, такая генерация кейсов — далеко не серебряная пуля и имеет свои недостатки.
Думаю, вы обратили внимание, что в процессе генерации кейсов происходит смешивание тестовых данных друг с другом. Из-за этого при падении кейса усложняется процесс выявления причины падения. Ведь часть параметров, задействованных в кейсе, никак не влияет на результат теста.
Это правда затрудняет разбор результатов тестов, с одной стороны. Но, с другой стороны, если тестируемый объект требует большой объем обязательных параметров, это тоже затрудняет нахождение причины бага.
Возвращаясь к самому началу статьи: при применении этого метода мы допускаем возможность пропуска багов, вызванных сочетанием двух и более параметров. Зато выигрываем в скорости, так что только вам решать, что важнее на каждом конкретном проекте.
Чтобы не пропускать баги дважды, мы ввели Zero Bug Policy и стали закрывать каждый пропущенный баг дополнительным тест-кейсом — уже не автоматически сгенерированным, а написанным вручную. Это дало отличные результаты: сейчас у нас более 150 блоков (тестируемых объектов), несколько релизов в день и от 0 до 3 пропущенных некритичных багов в месяц.
Если в вашем тестируемом объекте широкий набор входных параметров и вы хотите попробовать сократить количество кейсов и, как следствие, время на тестирование, я рекомендую попробовать приведенный выше метод генерации кейсов через один параметр.
На мой взгляд, для фронтовых компонентов он подходит идеально: можно более чем в три раза сократить время, например, на проверку внешнего вида через скриншот-тестирование. Да и разработка пойдет быстрее за счет появления кейсов на самых ранних этапах.
Конечно, если вы тестируете автопилот новой «Теслы» — нельзя пренебрегать даже малой вероятностью пропуска бага. Но в большинстве случаев не стоит забывать, что скорость в современном мире — это весьма важный критерий качества. А прирост скорости дает больше положительного результата, чем пара найденных минорных проблем.
А для самых ответственных в следующей статье я расскажу, как можно дополнительно предохраниться от хитрых багов, вызванных сочетанием параметров, при помощи пользовательских кейсов и StoryBook.
Именно так произошло в моем проекте, и сегодня я расскажу, как можно еще сильнее сократить количество тест-кейсов, не теряя при этом в качестве.
Тестируемый объект
Для начала расскажу немного о продукте. В Тинькофф наша команда разрабатывала блоки — это React-компоненты, состоящие из реализации и конфигурации. Реализация — это непосредственно сам компонент, который мы разработали и который видит пользователь в браузере. Конфигурация — это JSON, который задает параметры и наполнение этого объекта.
Главная задача блоков — быть красивыми и одинаково отображаться у разных пользователей. При этом от конфигурации и контента блок может очень значительно меняться.
Например, блок может быть таким — без фона, с кнопкой и картинкой справа:
Или вот таким — с фоном, без кнопки и с картинкой слева:
Или вообще вот таким — со ссылкой вместо кнопки и без списка в тексте:
Все примеры выше — это один и тот же блок, который имеет одну версию конфигурации (структура JSON, которую умеет обрабатывать конкретно этот React-компонент), но разное ее наполнение.
Сама схема:
{
components: {
background: color,
panel: {
panelProps: {
color: {
style: ['outline', 'color', 'shadow', 'custom'],
background: color
},
size: ['s', 'm', 'l'],
imagePosition: ['left', 'right']
},
title: {
text: text,
size: ['s', 'l'],
htmlTag: ['div', 'b', 'strong', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6']
},
description: {
text: html,
htmlTag: ['div', 'b', 'strong', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6']
},
image: {
alt: text,
title: text,
image: {
src: image,
srcset: [{
src: image,
condition: ['2x', '3x']
}],
webpSrcset: [{
src: image,
condition: ['1x', '2x', '3x']
}]
},
imageAlign: ['top', 'center', 'bottom']
},
button: {
active: boolean,
text: text,
color: {
style: ['primary', 'secondary', 'outline', 'outlineDark', 'outlineLight', 'textLink', 'custom'],
backgroundColor: color
},
onClick: {
action: ['goToLink', 'goToBlock', 'showBlock', 'crossSale', 'callFormEvent'],
nofollow: boolean,
url: url,
targetBlank: boolean,
title: text,
noindex: boolean,
guid: guid,
guidList: [{
guid: guid
}],
formId: guid,
crossSaleUrl: url,
eventName: text
},
htmlTag: ['div', 'b', 'strong', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6']
},
href: url
}
}
}
При этом у блока с картинкой справа будет значение
components.panel.imagePosition = right
. А у блока с картинкой слева — components.panel.imagePosition = left
. Для блока с кнопкой — components.button.active = true
и так далее. Надеюсь, принцип понятен. Таким образом задаются все параметры блока.Кейсы из сочетания параметров
В этой статье я не буду касаться вопросов версионирования схемы блоков, правил наполнения контентом или того, откуда в блок приходят данные. Все это — отдельные темы, которые никак не влияют на составление набора тест-кейсов. Главное, знать: у нас есть много параметров, влияющих на наш компонент, и каждый из них может принимать свой набор значений.
Для примера выше я выбрала блок с достаточно простой конфигурацией. Но даже в нем проверка всех сочетаний значений всех параметров займет непозволительно много времени, особенно если придется учитывать кроссбраузерность. Обычно здесь на помощь приходит Pairwise Testing, или попарное тестирование. Про него уже написаны тонны статей и есть даже обучения. Если вдруг не сталкивались — обязательно почитайте.
Давайте прикинем, сколько тест-кейсов у нас получится при его применении. Мы имеем больше 25 параметров, и некоторые из них принимают аж 7 и 9 вариантов значений. Да, можно чем-то пренебречь: например, если вы проверяете верстку, guid вам не важен. Но при применении Pairwise Testing все равно получится больше 80 тест-кейсов. И это, как я уже писала, для не самого сложного блока и без учета кроссбраузерности. Блоков у нас сейчас больше 150, и их количество растет, так что позволить себе столько кейсов мы не можем, если хотим сохранить скорость тестирования и выпуска новых версий.
Кейсы из одного параметра
Метод попарного тестирования основан на утверждении о том, что большинство дефектов вызвано взаимодействием не более двух факторов. То есть большинство багов проявляются либо на одном значении какого-то параметра, либо на сочетании значений двух параметров. Мы решили пренебречь второй частью этого утверждения и предположили, что при проверке одного параметра все равно будет найдено большинство багов.
Тогда получается, что для тестирования нам нужно проверить каждое значение каждого параметра хотя бы один раз. Но при этом каждый блок несет в себе всю конфигурацию. Тогда можно в каждом новом кейсе проверять максимум еще не проверенных значений, чтобы минимизировать количество кейсов.
Разберем алгоритм построения кейсов на упрощенном примере. Возьмем из нашей схемы компонент button и составим для него тест-кейсы:
button: {
active: boolean,
text: text,
color: {
style: ['primary', 'secondary', 'outline', 'custom'],
backgroundColor: color
}
Для упрощения примера я сократила длину списка в
button.color.style
.Шаг 1. Составляем варианты контента для каждого поля
Здесь все как в попарном тестировании: надо понять, какие значения может принять каждое из полей. Например, у
button.active
в нашем случае может быть всего два значения: true
или false
. Теоретически могут возникнуть еще варианты, например undefined
или отсутствие самого ключа. Здесь, на мой взгляд, важно очень четко определить границы и функциональность своей системы и не проверять лишнее. То есть если проверка на обязательность ключей или валидация значения реализованы в сторонней системе, то и проверять эту функциональность нужно в сторонней системе. А мы должны в качестве кейсов использовать только «правильные» данные.
По большому счету этот же принцип используется в пирамиде тестирования. При желании самые критичные интеграционные тесты можно добавить к нам — например, проверять обработку не пришедшего ключа. Но таких тестов должно быть минимальное количество. Иной подход — стремление к исчерпывающему тестированию, которое, как всем известно, невозможно.
Итак, мы определили варианты контента для каждого поля и составили такую таблицу:
В эту таблицу входит каждый класс эквивалентности каждого параметра, но только один раз.
Вот такие классы значений в нашем случае классы:
- text_s — короткая строка;
- text_m — более длинная строка;
- no_color — отсутствие цвета;
- rnd_color — любой цвет.
Шаг 2. Обогащаем таблицу данными
Поскольку каждый блок несет в себе полную конфигурацию, нам нужно добавить какие-нибудь релевантные данные в пустые ячейки:
Теперь каждый столбец — это один кейс.
При этом, поскольку недостающие данные мы выбираем сами, мы можем генерировать кейсы исходя из приоритета. Например, если мы знаем, что в кнопке короткий текст используется чаще, чем текст средней длины, то и проверять его стоит чаще.
В примере выше также можно обратить внимание на «выпавшие» кейсы — кейсы, в которых какой-то параметр вообще не проверяется, хотя и присутствует в таблице. В данном случае
button.color.style: secondary
не будет проверен на внешний вид, потому что неважно, какой стиль у отключенной кнопки. Чтобы «выпавшие» кейсы не привели к багам, раньше мы анализировали полученные наборы значений. Анализ происходил один раз при генерации тестовых наборов, и все «выпавшие» кейсы добавлялись вручную в итоговый тестовый набор. Такое решение проблемы достаточно топорное, но дешевое (если, конечно, у вас редко меняются конфигурации тестируемых объектов).
Более универсальное решение — разделить все значения на две группы:
- небезопасные значения (те, которые могут привести к «выпадению» кейсов);
- безопасные (которые не могут привести к «выпадению»).
Каждое небезопасное значение проверяется в своем тест-кейсе, обогатить кейс можно любыми безопасными данными. Для безопасных значений составляется таблица по инструкции выше.
Шаг 3. Уточняем значения
Теперь осталось только сгенерировать конкретные значения вместо классов эквивалентности.
Здесь каждому проекту придется подобрать свои варианты значений, опираясь на особенности тестируемого объекта. Некоторые значения генерировать очень просто. Например, цвет для большинства полей можно просто брать любой. Для некоторых блоков при проверке цвета приходится добавлять градиент, но он вынесен в отдельный класс эквивалентности.
С текстом чуть сложнее: если сгенерировать строку из случайных символов, то не будут протестированы переносы, списки, теги, неразрывные пробелы. Мы генерируем короткие и средней длины строки из реального текста, обрезая его до нужного количества символов. А в длинном тексте проверяем:
- html-тег (один любой);
- ссылку;
- непронумерованный список.
Этот набор кейсов вытекает непосредственно из нашей реализации блоков. Например, все html-теги подключаются вместе, поэтому и смысла тестировать каждый нет. При этом ссылка и список проверяются отдельно, потому что у них отдельная визуальная обработка (подкрашивание при наведении и буллиты).
Получается, что для каждого проекта нужно составить свой актуальный набор контента исходя из реализации тестируемого объекта.
Алгоритм
Конечно, на первый взгляд может показаться, что алгоритм сложный и не стоит потраченных усилий. Но если опустить все детали и исключения, которые я постаралась описать в каждом пункте выше, получается достаточно просто.
Шаг 1. Добавляем в таблицу параметров все возможные значения:
Шаг 2. Дублируем значения в пустые ячейки:
Шаг 3. Превращаем абстрактные значения в конкретные и получаем кейсы:
Каждый столбец таблицы — это один кейс.
Преимущества подхода
Такой способ генерации тест-кейсов имеет несколько важных преимуществ.
Меньше кейсов
Первое и очевидное — кейсов значительно меньше, чем в попарном тестировании. Если брать упрощенный пример с кнопкой — у нас получилось 4 кейса вместо 8 в попарном тестировании.
Чем больше параметров в тестируемом объекте, тем значительнее будет экономия кейсов. Например, для полного блока, представленного в начале статьи, у нас получается 11 кейсов, а с помощью попарного — 260.
Не раздувается количество кейсов при усложнении функциональности
Второй плюс — при появлении новых параметров, учитывающихся при тестировании, количество кейсов увеличивается далеко не всегда.
Например, пусть в нашу кнопку добавится параметр
button.color.textColor
с классами эквивалентности значений no_color
и rnd_color
. Тогда кейсов так и останется 4 штуки, просто в каждый их них добавится еще один параметр:Количество кейсов увеличится только в том случае, если у какого-то параметра станет больше значений, чем было кейсов.
Можно чаще проверять важное
За счет обогащения значениями (шаг 2 в алгоритме) можно чаще проверять более приоритетные или более рискованные значения.
Например, если мы знаем, что раньше пользователи чаще использовали короткий текст, а теперь — более длинный, мы можем обогащать кейсы более длинным текстом и чаще попадать в реальные пользовательские кейсы.
Можно автоматизировать
Алгоритм, приведенный выше, вполне поддается автоматизации. Конечно, сгенерированные алгоритмом кейсы будут меньше походить на реальные, чем сгенерированные человеком. Хотя бы за счет подбора цветов и обрезания текста.
Но зато уже в процессе разработки без участия тестировщика появляются кейсы, что очень сильно сокращает петлю обратной связи.
Недостатки
Естественно, такая генерация кейсов — далеко не серебряная пуля и имеет свои недостатки.
Сложно анализировать результат
Думаю, вы обратили внимание, что в процессе генерации кейсов происходит смешивание тестовых данных друг с другом. Из-за этого при падении кейса усложняется процесс выявления причины падения. Ведь часть параметров, задействованных в кейсе, никак не влияет на результат теста.
Это правда затрудняет разбор результатов тестов, с одной стороны. Но, с другой стороны, если тестируемый объект требует большой объем обязательных параметров, это тоже затрудняет нахождение причины бага.
Могут быть пропущены баги
Возвращаясь к самому началу статьи: при применении этого метода мы допускаем возможность пропуска багов, вызванных сочетанием двух и более параметров. Зато выигрываем в скорости, так что только вам решать, что важнее на каждом конкретном проекте.
Чтобы не пропускать баги дважды, мы ввели Zero Bug Policy и стали закрывать каждый пропущенный баг дополнительным тест-кейсом — уже не автоматически сгенерированным, а написанным вручную. Это дало отличные результаты: сейчас у нас более 150 блоков (тестируемых объектов), несколько релизов в день и от 0 до 3 пропущенных некритичных багов в месяц.
Выводы
Если в вашем тестируемом объекте широкий набор входных параметров и вы хотите попробовать сократить количество кейсов и, как следствие, время на тестирование, я рекомендую попробовать приведенный выше метод генерации кейсов через один параметр.
На мой взгляд, для фронтовых компонентов он подходит идеально: можно более чем в три раза сократить время, например, на проверку внешнего вида через скриншот-тестирование. Да и разработка пойдет быстрее за счет появления кейсов на самых ранних этапах.
Конечно, если вы тестируете автопилот новой «Теслы» — нельзя пренебрегать даже малой вероятностью пропуска бага. Но в большинстве случаев не стоит забывать, что скорость в современном мире — это весьма важный критерий качества. А прирост скорости дает больше положительного результата, чем пара найденных минорных проблем.
А для самых ответственных в следующей статье я расскажу, как можно дополнительно предохраниться от хитрых багов, вызванных сочетанием параметров, при помощи пользовательских кейсов и StoryBook.