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

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

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

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

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

Свойства:

  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 — довольно популярное средство решения конкретно этой задачи.

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

Похожие статьи:
[От редакции. Чтобы разговор получился интересным, мы пригласили провести интервью Дмитрия Шпаковского, Senior Software Engineer in Test в SurveyMonkey,...
Привет! Меня зовут Миша, и я Senior QА с коммерческим опытом более 6 лет. Сейчас я работаю в Provectus, но начинал я свой путь тестировщика...
Доступ до навчального курсу AI Essentials від Google на платформі Coursera стане безплатним для 2500 українців. Про це повідомляє видання...
На нашем YouTube канале появились новые видеоролики.Обзор джойстика Steelseries...
[В рубрике «Как я работаю» мы приглашаем гостя рассказать о своей...
Яндекс.Метрика