Мутаційне тестування
Мутаційні тестування (мутаційний аналіз або мутація програм) — це метод тестування програмного забезпечення, який включає невеликі зміни коду програми[1]. Якщо набір тестів не в змозі виявити такі зміни, то він розглядається як недостатній. Ці зміни називаються мутаціями і ґрунтуються на мутаційних операторах, які або імітують типові помилки програмістів (наприклад використання неправильної операції або імені змінної) або вимагають створення корисних тестів.
Простіше кажучи, мутаційне тестування призначене для оцінки якості тестового набору. У програму вноситься невелика зміна, яка має проявитися при тестуванні. Якщо вона ніяк не впливає на результати тестів, то це означає, що тести підібрані невдало.
Історичний огляд
Мутаційні тестування запропонував 1971 року студент Річард Ліптон[2] і вперше розробили та опублікували ДеМіл, Ліптон і Сейвард. Першу реалізацію інструменту для мутаційного тестування створив Тімоті Бадд з Єльського університету в своїй дисертації (з назвою «Мутаційний аналіз») 1980 року.
Метод мутаційного тестування вимагає великих обчислювальних витрат і до недавнього часу не був популярним. Проте останнім часом він знову викликає інтерес дослідників у галузі інформатики.
Огляд мутаційного тестування
Мутаційні тестування проводиться шляхом вибору мутаційних операторів та застосування їх одного за іншим до кожного фрагменту вихідного коду програми. Результат одного застосування мутаційного оператора до програми називається мутантом. Якщо набір тестів здатний виявити зміну (тобто один з тестів не проходить), то мутант називається вбитим. Як правило, мутант відрізняється від вихідної програми невеликим числом мутацій. У вихідній програмі можуть піддаватися мутаціям ділянки коду пов'язані з дефектами (змінюються значення змінних, модифікуються індекси та межі циклів, вносяться мутації в умови). Таким чином, з первісної програми шляхом внесення n
числа мутацій отримують k
мутантів, n >= k
(як мінімум одна мутація на одного мутанта). Якщо сформоване безліч тестових наборів виявляє всі мутації у всіх мутантів, то воно відповідає мутаційному критерію. Якщо тестування вихідної програми на заданій множині тестових наборів не виявило помилок, то програма оголошується коректною. У разі мутаційного тестування важливо створити таке число мутантів, яке б охоплювало всі можливі ділянки прояви помилок. Наприклад, розглянемо наступний фрагмент з програми на С++:
if (a && b) {
c = 1;
} else {
c = 0;
}
Оператор мутації умов замінить &&
на ||
, отримаючи наступного мутанта:
if (a || b) {
c = 1;
} else {
c = 0;
}
Для того, щоб тест міг вбити цього мутанта необхідно щоб були виконані наступні умови:
- Вхідні дані тесту повинні привести до різних станів програми для мутанта і вихідної програми. Наприклад, тест з
a = 1
іb = 0
призведе до цього. - Значення змінної
c
має вплинути на висновок програми і бути перевірено тестом.
Слабке мутаційне тестування (або слабке мутаційне покриття) вимагає виконання тільки першої умови. Сильне мутаційне тестування вимагає виконання обох умов і перевіряє що набір тестів в дійсності може виявити проблему. Слабке мутаційне тестування тісно пов'язане з методами покриття коду. Перевірка того, що набір тестів задовольняє слабку мутацію, вимагає набагато менше обчислень, ніж для сильної мутації.
Еквівалентні мутанти
Багато мутаційних операторів можуть призвести до еквівалентних програм. Наприклад, розглянемо наступний фрагмент програми:
int index = 0;
while (…) {
…;
index++;
if (index == 10) {
break;
}
}
Оператор мутації умов може замінити ==
на >=
отримуючи таким чином наступного мутанта:
int index = 0;
while (…) {
…;
index++;
if (index >= 10) {
break;
}
}
Однак не існує тесту, який міг би вбити цього мутанта. Отримана програма еквівалентна вихідній програмі. Такі мутанти називаються еквівалентними мутантами.
Розпізнавання еквівалентних мутантів є одним з найбільших перешкод для використання мутаційного тестування на практиці. Зусилля для перевірки того, чи є мутант еквівалентним, можуть бути дуже великими навіть для невеликих програм[3].
Мутаційні оператори
Багато видів мутаційних операторів були досліджені. Наприклад, для імперативних мов наступні оператори можуть бути використанні:
- Видалити оператор програми.
- Замінити кожне логічне вираження на логічну Констану «істина» або «фальш».
- Замінити кожну арифметичну операцію на іншу. Наприклад,
+
на*
,-
або/
. - Замінити кожну логічну операцію на іншу. Наприклад,
>
на>=
,==
або<=
. - Замінити кожну змінну на іншу (з тієї ж області видимості). Обидві змінні повинні мати однакові типи.
Крім того існують оператори для об'єктно-орієнтованих мов[4], оператори для паралельного програмування[5], оператори для структур даних, таких як контейнери[6] та ін.
Примітки
- A Practical System for Mutation Testing: Help for the Common Programmer by A. Jefferson Offutt.
- Mutation 2000: Uniting the Orthogonal
- P. G. Frankl, S. N. Weiss, and C. Hu. All-uses versus mutation testing: An experimental comparison of effectiveness. Journal of Systems and Software, 38:235-253, 1997.
- MuJava: An Automated Class Mutation System by Yu-Seung Ma, Jeff Offutt and Yong Rae Kwo.
- Mutation Operators for Concurrent Java (J2SE 5.0) by Jeremy S. Bradbury, James R. Cordy, Juergen Dingel.
- Mutation of Java Objects by Roger T. Alexander, James M. Bieman, Sudipto Ghosh, Bixia Ji.