Критерии полноты тестового покрытия в генетических алгоритмах генерации тестов

Критерии полноты тестового покрытия в генетических алгоритмах генерации тестов1

М.А. Владимиров т\>1ас1(д)}8рга8. ги

Аннотация. В статье описывается применение генетических алгоритмов для автоматической генерации тестов. Проводится анализ некоторых широко распространённых критериев полноты на предмет их применимости для построения тестов с помощью генетических алгоритмов. Строятся оценочные функции, соответствующие этим критериям.

1. Введение

При разработке и сопровождении программного обеспечения, значительная часть усилий тратится на поиск и устранение ошибок. Самым распространённым методом поиска ошибок является тестирование, то есть процесс выполнения программ с целью обнаружения ошибок [1]. Здесь слово «программа» понимается в широком смысле, как любая запись алгоритма. В частности, программами являются отдельные процедуры, функции, классы и т.д.

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

Для количественной оценки качества тестирования используются различные метрики тестового покрытия [9]. Для качественного тестирования необходимо построить полный тестовый набор, то есть набор, удовлетворяющий некоторому критерию полноты. Зачастую критерий полноты для тестового набора определяют через пороговое значение метрики тестового покрытия. Построение полного тестового набора для больших систем вручную может быть крайне трудоёмкой задачей. Автоматизация этого процесса позволяет

1 Работа поддержана грантом РФФИ (05-01-999).

существенно снизить затраты на тестирование. Существуют различные подходы к решению задачи автоматической генерации тестов: [3,4,6]. Один из них основан на применении генетических алгоритмов [8]. Этот подход во многих случаях даёт хорошие результаты. К сожалению, его эффективность существенно зависит от используемого критерия полноты. Цель данной статьи — проанализировать некоторые широко распространённые критерии полноты тестового набора на их применимость при использовании генетических алгоритмов для генерации тестов.

2. Основные понятия

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

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

Общая схема генетического алгоритма выглядит следующим образом:

• создать начальный набор кандидатов;

• оценить качество каждого кандидата в текущем наборе;

• выбрать пары наиболее качественных кандидатов для воспроизводства;

• применить оператор кроссовера;

• применить оператор мутации;

• если не выполнено условие останова, перейти к шагу 2.

Начальный набор кандидатов, как правило, формируется случайным образом. На множестве кандидатов определяется оценочная функция, задающая качество кандидата, то есть то, насколько он близок к верному решению. При выборе кандидатов для воспроизводства более качественные кандидаты имеют больше шансов. По двум выбранным кандидатам предыдущего поколения оператор кроссовера строит кандидата следующего поколения. Оператор мутации вносит малые случайные изменения кандидатов. Алгоритм завершается, когда выполняется условие останова. Часто используются следующие условия останова:

• достигается заданное количество поколений;

• найдено верное решение;

• за заданное количество итераций максимальное качество кандидатов в популяции не улучшилось;

• различные комбинации предыдущих условий.

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

2.2. Критерии полноты тестового покрытия

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

Пусть Р — множество программных систем, Т — множество тестов, а Е — множество тестовых наборов, то есть множество всех конечных подмножеств множества Т. Тогда задача генерации тестов может быть сформулирована следующим образом: для заданной тестируемой системы $ е Р построить тестовый набор О е Е, удовлетворяющий заданному критерию полноты тестового покрытия Р : Р х Е —> {• , _1_}, то есть такой набор О , для которого ^(5”, сг) =• .

Многие критерии полноты тестового покрытия, имеющие практическое применение, строятся по следующей схеме: для тестируемой системы 5* критерий Р определяет множество элементов тестового покрытия .

Элементом тестового покрытия можно считать некоторый класс событий, которые могут произойти в ходе работы тестируемой программной системы. По появлению в процессе исполнения программы элементов тестового покрытия и различных их комбинаций можно судить о полноте или качестве проверки, которую выполняет данный тестовый набор. Например, элементами тестового покрытия могут быть исполняемые строки исходного кода (соответствующие событиям их исполнения); рёбра графа потока управления; пути в графе потока управления; логические выражения, встречающиеся в исходном коде и т.п. Кроме того, критерий Р определяет логическую функцию / : х У —»{• которая принимает значение /(£/,/) =• ,

если элемент тестового покрытия q покрывается тестом /. Тестовый набор О для системы Л' удовлетворяет критерию полноты тестового покрытия /’ , если каждый элемент тестового покрытия из множества покрывается хотя бы одним тестом из тестового набора О . Иными словами:

Р{8,а) = ^ц^01

Приведём несколько примеров часто упоминаемых критериев полноты тестового покрытия:

• каждый оператор в исходном коде выполняется хотя бы один раз;

• каждая ветвь графа потока управления выполняется хотя бы один раз;

• каждый путь графа потока управления выполняется хотя бы один раз;

• каяедое логическое выражение хотя бы один раз вычисляется со значением «истина» и хотя бы один раз - со значением «ложь»;

• тестовый набор убивает всех мутантов из заданного набора.

Заметим, что все критерии, приведённые в качестве примеров, соответствуют ранее изложенной схеме.

2.3. Метрики тестового покрытия

Со многими критериями полноты тестового покрытия можно связать соответствующую метрику тестового покрытия. Метрика тестового покрытия — это функция вида М : Р х Е —> і? . Значение этой функции М(5,сг^ имеет смысл числовой оценки того, насколько хорошо тестовый набор О покрывает тестируемую систему Л'. Сам критерий при этом можно записать в виде М(8,сг) > 0С5, где 0С5 — это минимальное пороговое значение метрики М для тестируемой системы Л'.

В частности, для критерия полноты тестового покрытия , представимого в виде (1), можно ввести следующую метрику:

Мр^&) =| {д Є <£ : Зґ є а: Дд,і) =• } |

Сам критерий при этом примет вид:

ІІЧ є <21: Зґ є сг: /

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

I {ч є 01: з* Є сг: /

Параметр Я є (0,1] указывает, какая доля элементов тестового покрытия

должна быть покрыта тестовым набором. Приведём несколько примеров часто упоминаемых метрик тестового покрытия:

• количество покрытых (выполненных хотя бы один раз) операторов в исходном коде;

• количество покрытых ветвей графа потока управления;

• количество покрытых путей графа потока управления;

• количество распознанных мутантов (версий тестируемой системы с искусственно привнесёнными ошибками).

Все эти метрики могут быть представлены в виде (2). Подробное описание этих и других, используемых на практике, метрик полноты тестового покрытия можно найти в [9].

3. Генетический алгоритм генерации тестов

Рассмотрим следующую задачу генерации тестов:

Задача 1. Для заданной тестовой системы Л' построить тестовый набор О е Е , удовлетворяющий критерию (4).

Для построения генетического алгоритма решения этой задачи необходимо определить:

• множество кандидатов;

• структуру представления кандидатов;

• оценочную функцию;

• оператор кроссовера;

• оператор мутации;

• условие останова.

3.1. Простейший алгоритм

Рассмотрим простейший генетический алгоритм для решения задачи 1. В качестве множества кандидатов возьмём множество Е ; в качестве оценочной

функции возьмём метрику тестового покрытия МР (5, а) для заданной

тестируемой системы Л'. Условием останова будет наличие в текущем поколении решения о, удовлетворяющего критерию (4). Структуру представления кандидатов, а также операторы кроссовера и мутации мы пока уточнять не будем.

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

Заметим также, что каждое последующее поколение тестов формируется путём применения операторов кроссовера и мутации к тестам из предыдущего поколения. Если в предыдущем поколении не было ни одного теста, покрывающего некоторый элемент тестового покрытия С]. то в последующем поколении такой тест может появиться только как результат кроссовера или мутации тестов, не покрывающих Ц. Как бы мы не определяли операторы кроссовера и мутации, нет никаких оснований полагать, что получить таким способом тест, покрывающий С], проще, чем при полностью случайной генерации.

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

3.2. Целенаправленный поиск

Учитывая структуру критерия (4), из задачи 1 можно выделить следующую подзадачу:

Задача 2. Для заданной тестовой системы Л' и заданного элемента тестового покрытия С], построить тест /се/, удовлетворяющий условию / (с/, I) =• .

Для решения исходной задачи 1, достаточно решить задачу 2 для и > Л \ \

попарно различных элементов тестового покрытия £/,, £/2,..., С)п е , то есть построить тесты , /2,..., 1п такие, что

Решением задачи 1 будет множество ,..., }.

Рассмотрим генетический алгоритм решения задачи 2. В качестве множества кандидатов возьмём множество тестов Т. Условие останова: в текущей популяции присутствует тест С] такой, что /(V/, I) =• . Оценочная функция

П1ч : / —> Н каждому тесту I ставит в соответствие числовую меру (!) того, насколько тест / близок к тому, чтобы покрыть элемент тестового покрытия С]. При этом оценочная функция fq достигает своего максимального значения на тех и только на тех тестах, которые удовлетворяют условию / (с/, I) =• . Иными словами:

В частности, в качестве оценочной функции можно использовать следующую функцию, удовлетворяющую условию (5):

В такой оценочной функции считается, что все тесты, не покрывающие элемент тестового покрытия Ц. одинаково далеки от того, чтобы покрыть элемент С]. При использовании этой оценочной функции эффективность генетического алгоритма будет не выше, чем при случайном поиске. Примеры более эффективных оценочных функций для некоторых метрик полноты тестового покрытия можно найти в [8,5,2].

/

0,при/(?,0 =1,

1,при/(^,0=* •

4. Оценочные функции

В этом разделе подробно рассматриваются три известных критерия полноты тестового покрытия, и для каждого из них предлагается оценочная функция.

4.1. Покрытие операторов исходного кода

Тестовый набор удовлетворяет критерию покрытия операторов исходного кода, если при выполнении этого тестового набора каждый оператор исходного текста программы выполняется хотя бы один раз. Элементами тестового покрытия в данном случае являются операторы исходного текста. Для заданного оператора Ц значение оценочной функции (!) тем больше, чем ближе тест / к тесту, покрывающему оператор С]. Для построения оценочной функции рассмотрим граф потока управления тестируемой системы Л'. Вершинами графа являются операторы исходного кода, то есть множество . В графе существует ребро, идущее из вершины в вершину С/2 тогда и только тогда, когда оператор С]2 может быть выполнен непосредственно после

оператора . Пусть П(с/,/) — это множество всех элементов С)' из , для которых выполняются следующие условия:

• существует путь в графе потока управления ведущий из с( в q или

ч' = ч\

• /(^,0=* •

Обозначим через б//Л/(</ ,£/) длину кратчайшего пути в графе потока управления, ведущего из С/' в С/ (сИ.\1(с/, ц) = 0). Тогда оценочную функцию можно определить следующим образом:

-т

д'еП(д,0

Выражение, стоящее справа, определяет, за какое минимальное количество переходов можно добраться до элемента покрытия С] от уже покрытых

элементов из множества (У^ . Функция (!) принимает значение 0 на тех и только тех тестах, которые покрывают элемент q. Заметим, что

14(0 = 0 •о /(?,0=в;

[даД0< 0 <=>

4.2. Покрытие ветвей потока управления

Тестовый набор удовлетворяет критерию покрытия ветвей потока управления, если при выполнении этого тестового набора управление хотя бы один раз проходит по каждому ребру графа потока управления. Заметим, что любой

63

тестовый набор, удовлетворяющий этому критерию, удовлетворяет также и критерию покрытия операторов исходного кода. Обратное утверждение, однако, неверно [9]. Элементами тестового покрытия являются переходы в графе потока управления. С каждым переходом в графе потока управления можно связать условие, при котором этот переход может быть выполнен. Переход от оператора q к оператору г, с которым связано условие р,

обозначим как q —-—> Г . Для выполнения перехода q —-—> Г необходимо и достаточно, чтобы был выполнен оператор q, и чтобы после этого условие р обратилось в истину. Соответственно, для тестов, не покрывающих оператор q, в качестве оценочной подходит функция , определённая уравнением (6), так как истинность условия р для оценки таких тестов роли не играет. Для тестов, покрывающих оператор q, функция (!) обращается в 0 . Для таких

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

(0, пРи /(Я, 0 =1,

к, Р(0>при/(?,о=в .

д,р

Значение функции /л р'.Т —> R тем больше, чем ближе заданный тест к тесту, в котором условие р выполняется после выполнения оператора q . При этом функция [Л (t) достигает своего максимума на тех и только тех тестах, в которых после выполнения оператора q выполняется условие р, то есть тех, которые покрывают переход q —-—> г .

Функцию ц (t) можно определять по-разному в зависимости от характера

условия р . Если условие имеет форму простого (не)равенства хОу , где « 0 » обозначает одно из отношений «<», «>», « = », «<» или «>», то для определения функции /л (t) можно использовать значение X — у \. например, следующим образом:

2, при хОу, е-,х-у>, при -i(xOy).

Если условие представляет собой конъюнкцию р = Р\ л Р2 л • • • л Рп, ТО В качестве значения функции [Л (t) можно взять количество членов этой

^(0 =

конъюнкции, принимающих значение «истина». В общем случае эффективно определить функцию j-lq (t) затруднительно.

4.3. Покрытие путей потока управления

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

R = тх,т2,...,тп , где гг имеет вид qt —-—>qj+l, при 1 < i < п . Упорядоченным подмножеством пути Т1,Т2,...,ТП назовём последовательность Г, , Г, ..... Г, такую, что 1 < /, < /'. < ... < <п.

Ч г2 гт т

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

Пусть есть два пути R' = Т\, Т’2,и R" = Т"ъТ”2,...,т"к, и пусть

r't =

т i2 — Т j2,

<

J im ~ Т im ’

причём 1 < /j < /2 < ... < im < I и 1 < jx< /2 < ... < jm<k . Тогда пути R' и

R" имеют общее упорядоченное подмножество размера m .

Обозначим через length(K) длину пути R, а через common( R\ R") -

максимальный размер общего упорядоченного подмножества путей R' и R" . Определим оценочную функцию для критерия покрытия путей потока

управления следующим образом:

-mR (/) = length{R) + length(path(t)) - 2 • common(R, path(t)).

Здесь path(t) — это путь, по которому приходит управление при

выполнении теста t. Значение в правой части равно количеству переходов в путях R и path(t), не входящих в максимальное общее упорядоченное подмножество этих путей. Оно равно 0 тогда и только тогда, когда пути R и path(t) совпадают.

5. Заключение

Применение генетических алгоритмов для генерации тестов предъявляет дополнительные требования к используемым критериям полноты тестового покрытия. Эго вызвано тем, что критерий полноты используется не только для оценки качества сгенерированных тестов, но и непосредственно в процессе генерации для оценки близости полученных тестов к нужным результатам. Таким образом, нужно иметь оценочную функцию, позволяющую измерить эту близость, определить, насколько перспективными являются уже построенные тесты с точки зрения их использования в качестве основы для построения новых тестов. Кроме того, нужно иметь в виду, что тривиальные решения — функции вида «покрыто - О, не покрыто - 1» — работают очень плохо.

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

Литература

1. Г. Мейерс. Искусство тестирования. М.: Финансы и статистика, 1982.

2. Andre Baresel, Harmen Sthamer, and Michael Schmidt. Fitness function design to improve evolutionary structural testing. In GECCO, pages 1329-1336, 2002.

3. Chandrasekhar Boyapati, Sarfraz Khurshid, and Darko Marinov. Korat: Automated testing based on Java predicates. In Proceedings of the International Symposium on Software Testing and Analysis (ISSTA 2002), Rome, Italy, 22-24 July 2002. IEEE.

4. Ugo A. Buy, Alessandro Orso, and Mauro Pezze. Automated testing of classes. In ISSTA, pages 39^18,2000.

5. Yoonsik Cheon and Myoung Kim. A fitness function for modular evolutionary testing of object-oriented programs. Technical Report 05-35, Department of Computer Science, University of Texas El Paso, Nov 2005.

6. Yoonsik Cheon, Myoung Kim, and Ashaveena Perumendla. A complete automation of unit testing for Java programs. In Hamid R. Arabnia and Hassan Reza, editors, Proceedings of the 2005 International Conference on Software Engineering Research and Practice (SERP ’05), Volume I, Las Vegas, Nevada, June 27-29, 2005, pages 290-295. CSREA Press, 2005.

7. John H. Holland. Adaptation in natural and artificial systems. MIT Press, Cambridge, MA, USA, 1992.

8. Paolo Tonella. Evolutionary testing of classes. In ISSTA ’04: Proceedings of the 2004 ACM SIGSOFT international symposium on Software testing and analysis, pages 119-128, New York, NY, USA, 2004. ACM Press.

9. Hong Zhu, Patrick A. V. Hall, and John H. R. May. Software unit test coverage and adequacy. ACMComput. Surv., 29(4):366^127,1997.