Автоматическая генерация тестов: подходы и инструменты

Можно ли автоматизировать не тестирование, а процесс создания тестов? Конечно, это, как правило, более сложная задача, но и она вполне успешно решается для отдельных (и при этом часто встречающихся) случаев. Тест-дизайн на основании данных, когда сценарии относительно неизменны, но меняются входные данные, — первый и очевидный кандидат на попытку автоматизировать этот процесс. Тесты, основанные на поведении, состояниях и переходах, — более противоречивая область, и в этой статье она затронута не будет.

Рассмотрим относительно стандартные подходы к автоматической генерации данных, инструменты, которые реализуют некоторые из них, сферы их применения (здесь, как мне кажется, речь идет не о преимуществах и недостатках, а скорее о выборе правильного инструмента для правильной задачи).

Случайная генерация

Идея: данные относятся к каким-то множествам или диапазонам, каждый тест — комбинация значений, каждое из которых выбирается случайно из соответствующего множества/диапазона (класса эквивалентности).

Свойства:

  1. Очень просто реализовать, есть инструменты, которые позволяют автоматически генерировать синтаксически корректные юнит-тесты (в рамках синтаксиса какого-либо ЯП и/или библиотеки).
  2. Можно очень быстро сгенерировать очень много тестов.
  3. Нахождение ошибок столь же случайно, как и сами входные данные.

Сферы применения:

  1. Фаззинг.
  2. Небольшие программы или части программ, где количество элементов внутри классов эквивалентности не слишком велико.

Генерация на основании алгоритмов поиска

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

Свойства:

  1. Можно «застрять» в локальном оптимуме, если минимально необходимый порог слишком низок (тем не менее задача будет решена).
  2. Как следствие п. 1, очень часто возможно несколько разных решений и нет уверенности, что то, которое было сгенерировано, наилучшее.
  3. Важно понимать, что, как и при случайной генерации, такие тесты не принимают во внимание бизнес-логику, техники тест-дизайна и прочее, так что качество тестирования и, следовательно, найденные баги также относительно случайны.

Сферы применения:

  1. Очень широкий спектр задач, есть инструменты, которые используют этот подход как для генерации юнит-тестов, так и для случайной генерации.
  2. Как правило, в качестве целевого показателя используют процент покрытия и задают необходимое минимальное значение.

Генетические алгоритмы

Идея: сгенерировать очень много наборов данных (популяции), менять компоненты между наборами (кроссовер, в результате получается мутировавший набор), для каждого набора данных (в том числе исходных) измерять значение целевой функции и сравнивать с минимально необходимым порогом. В целом похоже на взгляд со стороны теории оптимизации, однако есть возможность выйти из локального оптимума в результате кроссовера, то есть, предположительно, результаты будут лучше. Но и работать такие алгоритмы будут медленнее, чем алгоритмы поиска, рассмотренные выше.

Свойства:

  1. Можно получить несколько решений, удовлетворяющих целевой функции.
  2. Можно иметь несколько целевых функций, поскольку сначала создаются наборы и только потом измеряется целевой показатель. Этот подход позволяет взять столько целевых функций, сколько нужно, и рассматривать их все одновременно.

Сферы применения:

  1. Академический интерес :) Из-за ограничений в производительности не нашла свидетельств применения генетических алгоритмов в реальной практике.
  2. Несколько целевых показателей, которые нужно учитывать одновременно.

Генерация тестовых последовательностей

Идея: классические комбинаторные техники тест-дизайна — взять параметры, выбрать значения, задать ограничения (правила комбинирования) и получить все возможные комбинации, удовлетворяющие ограничениям. Pairwise является частным случаем этого подхода, как и причина/следствие (хоть это и более общий метод).

Свойства:

  1. На вход таким алгоритмам подается формальная модель, то есть параметры и значения, которые они принимают, условия, которые накладываются на комбинации таких значений, параметры комбинаций (например, каждый с каждым, только пары, только тройки и т. д.). Следовательно, результат проектирования полностью предсказуем и обладает строго тем набором свойств, которые заложены в модели.
  2. В дополнение к правилам генерации и заданным зависимостям между значениями параметров можно устанавливать веса значений. Таким образом, можно регулировать частоту встречаемости значений в тестах: там, где в комбинациях все равно, какое значение выбрать, вес задает вероятность такого выбора.
  3. Кроме весов значений, можно задавать и приоритизацию, то есть порядок, в котором тесты появятся в наборе. Плохая новость в том, что такая возможность есть не во всех инструментах.

Сферы применения:

  1. Тесты на любом уровне, которые учитывают бизнес-логику приложения, так как формальная модель на входе таким алгоритмам задается ее автором.
  2. Важно понимать, что инструменты, работающие с таким подходом, будут генерировать только данные (комбинации значений), но не сами тесты, о каком бы уровне ни шла речь (цена за учтенную бизнес-логику приложения). Это значит, что как юнит-тесты, так и автотесты на системном уровне придется писать самостоятельно, зато данные для них можно будет считать из файлов, которые получены с помощью инструментов.

Инструменты

Ниже речь пойдет об инструментах, которые реализуют некоторые из описанных выше подходов: как воспользоваться, что получится, какой подход используется. Можно сравнить и выбрать то, что подходит для конкретной задачи, или как минимум определиться, что именно искать дальше.

Генерация юнит-тестов на Java с Randoop (случайная генерация)

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

  1. Создать файл myclasses.txt со списком имен классов, которые подлежат тестированию.
  2. Вызвать Randoop: java -classpath $(RANDOOP_JAR) randoop.main.Main gentests --classlist=myclasses.txt --time-limit=60
  3. Скомпилировать и запустить тесты, которые сгенерировал Randoop. Получится два набора: ErrorTest и RegressionTest. Тесты из первого набора не пройдут (предположительно, баги, требуется дополнительное исследование). Тесты из второго набора пройдут успешно.

Что примерно получится:

	@Test
	public void test001() throws Throwable {

		if (debug) { System.out.format("%n%s%n","RegressionTest0.test001"); }

		MerArbiter.TestMyMerArbiterSym testMyMerArbiterSym0 = new MerArbiter.TestMyMerArbiterSym();
		testMyMerArbiterSym0.run2((int)'a', true, (int)'a', false, (int)'4', (int)(byte)100, true, (int)(short)100, false, (int)' ', 10, false, 100, false, (-1), (int)' ', false, (int)(short)(-1), true, 0, true, true, (int)'a', (int)(short)1);
		testMyMerArbiterSym0.run2((int)(byte)100, false, 10, true, (int)'4', (int)' ', false, 1, false, 100, 100, false, (int)(byte)(-1), false, (int)'#', (int)(byte)1, false, 0, true, (int)(byte)100, true, true, (int)(short)10, (int)(short)10);
		testMyMerArbiterSym0.run2((int)(short)100, false, 0, false, (int)(byte)1, (int)(byte)1, true, (int)(byte)0, true, (int)(byte)100, (int)'a', true, (int)' ', false, (int)(byte)1, (int)(byte)100, false, (int)'4', false, (int)' ', true, false, (int)(short)100, (int)'4');

	}

Генерация юнит-тестов на Java с EvoSuite (алгоритмы поиска)

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

  1. Определиться с классом, который будет тестироваться, путем к этому классу и его зависимостям.
  2. Определиться с целевой функцией.
  3. Вызвать EvoSuite, например так (задан целевой параметр): $EVOSUITE -class <ClassName> -projectCP <ClassPath> -criterion branch

Что примерно получится:

public class MyMerArbiterSymTest {
	MyMerArbiterSym o = new MyMerArbiterSym();
	@Test
	public void test0() {
		o.run2(3215, false, 3215, false, -633, -1952, false, 3215, false, 0, -2384, false, 3215, false, 3215, -90, false, -90, false, 599, false, true, 0, -1952);
	}
	@Test
	public void test1() {
		o.run2(0, true, 0, true, -1, 562, false, 0, false, -1, 0, false, 2844, true, 1354, 0, false, 1, true, 0, false, false, 0, -2561);
	}
	@Test
	public void test2() {
		o.run2(562, true, -1, false, -1448, -3029, false, 11, false, 0, 0, false, 0, false, 0, -3029, false, 0, false, -1218, false, false, 562, -1);
	}
	@Test
	public void test3() {
		o.run2(0, false, 0, false, 0, 2, false, 0, false, 0, -1, false, 11, false, 0, -3029, false, 0, false, 0, false, false, 1, -3884);
	}

}

Генерация данных с PICT (комбинаторное тестирование)

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

1. Задать модель и ее ограничения, например так:

BROWSER: IE, Firefox, Chrome, Opera
LANG: en, ru, ua
OS: win, linux, android
{BROWSER, LANG, OS} @ 1
IF [OS] = "linux" THEN [BROWSER] <> "IE";

2. Запустить PICT, передав модель на вход, перенаправить вывод в файл при необходимости: pict.exe model.txt > results.csv

Что примерно получится:

Для модели выше, скажем, получится файл (или вывод в консоль) вот с такими данными:

IE	       	ua	win
Firefox	  	en	win
Opera		ua	linux
Chrome		ru	android

Что с этим потом делать — на усмотрение автора модели или других причастных.

Полезные ссылки

Выводы

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

Главная сложность здесь, естественно, не в инструментах, а в моделировании, особенно для приложений со сложной логикой (что встречается не так уж часто). Случайная генерация в контексте тестирования чаще всего была полезна в фаззинге, но тут приходилось изобретать несколько велосипедов, писать свою логику генерации случайных данных вместо использования стандартных инструментов.

На юнит-уровне, за который, как правило, отвечают программисты, гораздо чаще используются инструменты и подходы, основанные на алгоритмах поиска. Это позволяет достигать поставленных целей по уровню покрытия кода, хотя чаще всего не приносит ожидаемого уровня качества. EvoSuite — довольно популярное средство решения конкретно этой задачи.

Надеюсь, что-то из вышеперечисленного пригодится и вам.

Похожие статьи:
Від початку війни багато спеціалістів української IT-сфери взяли зброю до рук і боронять Україну, а дехто з них активно волонтерить...
В українському офісі ІТ-компанії Luxoft розглядають варіанти оформлення digital nomad visas (віза віддаленого працівника — ред.) для своїх...
Сеть интернет-телефонии SIPNET объявила о начале публичного тестирования технологии WebRTC (Web Real Time Communication).Это стало возможным, со...
У січні 2023-го фандрейзингова платформа UNITED24 запустила новий напрям — «Відбудова України». Його мета — зібрати кошти...
У січні 2022 року Sii Ukraine стала дочірньою компанією Sii Poland (є частиною великого світового аутсорсера Sii Group)....
Яндекс.Метрика