Мутаційне тестування (Mutation testing)
Юніт тести допомагають нам переконатися, що код працює так, як ми цього хочемо. Однією із метрик тестів є відсоток покриття рядків коду (Line Code Coverage). Але наскільки коректний цей показник? Чи має він практичний зміст і чи можемо ми йому довіряти? Адже якщо ми видалимо всі assert рядки з тестів, або просто замінимо їх на assertSame(1, 1), то як і раніше матимемо 100% Code Coverage, при цьому тести зовсім не тестуватимуть нічого. Наскільки ви впевнені у своїх тестах? Чи вони покривають всі гілки виконання ваших функцій? Чи тестують вони взагалі хоч щось? Відповідь це питання дає мутаційне тестування.
Мутаційне тестування (MT, Mutation Testing, Mutation Analysis, Program mutation, Error-based testing, Fault-based testing strategy) - це вид тестування програмним забезпеченням методом білої скриньки, заснований на всіляких змінах (мутаціях) частин вихідного коду та перевірці реакції на ці зміни набору автоматичних юніт тестів. Зміни у мутантній програмі зберігаються вкрай невеликими, тому це не впливає на загальне виконання програми. Якщо тести після зміни коду не падають (failed), значить цей код недостатньо покритий тестами, або написані тести марні. Критерій, що визначає ефективність набору автоматичних тестів, називається Mutation Score Indicator (MSI).
Введемо деякі поняття з теорії мутаційного тестування:
Для застосування цієї технології у нас, очевидно, має бути вихідний код (source code), деякий набір тестів (для простоти говоритимемо про модульні - unit tests). Після цього можна починати змінювати окремі частини вихідного коду та дивитися, як реагують на це тести. Одну зміну вихідного коду називатимемо Мутацією (Mutation). Наприклад, зміна бінарного оператора "+" на бінарний "-" є мутацією коду. Результатом мутації є Мутант - це новий мутований код. Кожна мутація будь-якого оператора у коді (а їх сотні) призводить до нового мутанта, для якого повинні бути запущені тести. Крім зміни "+" на "-", існує безліч інших мутаційних операторів (Mutation Operator, Mutator, faults or mutation rules), кожен з яких має свою мету та застосування:
Мутація значень (Value mutation): зміна значення параметра чи константи;
Мутація операторів (Statement mutation): реалізується шляхом редагування, видалення чи перестановки оператора;
Мутація рішення (Decision Mutation): зміна логічних, арифметичних та реляційних операторів.
Залежно від результату тесту мутанти поділяються на:
Мутанти, що вижили (Survived Mutants): мутанти, які все ще живі, тобто не виявляються при виконанні тесту. Їх також називають live mutants;
Вбиті мутанти (Killed Mutants): мутанти, виявлені тестами;
Еквівалентні мутанти (Equivalent Mutants): мутанти, які змінивши частини коду не призвели до будь-якої фактичної зміни у виведенні програми, тобто. вони еквівалентні вихідному коду;
Немає покриття (No coverage): у разі мутант вижив, оскільки цього мутанта не проводилися тести. Цей мутант знаходиться в частині коду, що не зачепила жодного з ваших тестів. Це означає, що наш тестовий приклад не міг його охопити;
Тайм-аут (Timeout): виконання тестів із цим активним мутантом призвело до тайм-ауту. Наприклад, мутант призвів до нескінченного циклу у вашому коді. Не зважайте на цього мутанта. Він вважається "виявленим". Логіка тут у тому, що якщо цей мутант буде впроваджений у ваш код, ваша CI-складання виявить його, тому що тести ніколи не завершаться;
Помилка виконання (Runtime error): виконання тестів спричинило помилку (а чи не провалу тесту). Це може статися, коли засіб запуску тестів не працює. Наприклад, коли засіб виконання тесту видає помилку OutOfMemoryError або динамічних мов, коли мутант привів до нерозбірливого коду. Не витрачайте занадто багато уваги цього мутанта. Він не відображається у вашій оцінці мутації;
Помилка компіляції (Compile error): цей стан виникає, коли це мова, що компілюється. Мутант спричинив помилку компіляції. Він не відображається в оцінці мутації, тому вам не потрібно приділяти занадто багато уваги до вивчення цього мутанта;
Ігнорується (Ignored): ми можемо бачити цей стан, коли користувач встановлює конфігурації для його ігнорування. Він буде відображатись у звітах, але не вплине на оцінку мутації;
Тривіальні мутанти (Trivial Mutants): практично нічого не роблять. Будь-який тестовий приклад може вбити цих мутантів. Якщо наприкінці тестування залишилися тестові приклади, це неприпустимий мутант (invalid mutant).
Метрики:
Виявлені (Detected): це кількість мутантів, виявлених нашим тестом, тобто убитих мутантів. Detected = Killed mutants +Timeout;
Невиявлені (Undetected): це кількість мутантів, які не були виявлені нашим тестом, тобто мутантів, що вижили. Undetected = Survived mutants + No Coverage;
Покриті (Covered): це кількість мутантів, покритих тестами. Covered = Detected mutants + Survived mutants;
дійсні (Valid): це кількість дійсних мутантів, які не викликали помилки компіляції або рантайму. Valid = Detected mutants + Undetected mutants;
Недійсні (Invalid): кількість всіх недійсних мутантів, тобто. вони були протестовані, оскільки викликали помилку компіляції чи виконання. Invalid = Runtime errors + Compile errors;
Усього мутантів (Total mutants): містить усіх мутантів. Total = Valid + Invalid + Ignored;
Оцінка мутації на основі покритого коду (Mutation score based on covered code): оцінює загальний процент убитих мутантів на основі покриття коду. Mutation score based on covered code = Detected / Covered * 100;
Неправильний синтаксис (Incorrect syntax): їх називають мертвонародженими мутантами, це як синтаксична помилка. Зазвичай, ці помилки повинен виявляти компілятор;
Оцінка мутації (Mutation score): це оцінка, заснована на кількості мутантів. В ідеалі дорівнює 1 (100%). Mutation score = Detected / Valid * 100 (%).
Джерела:
Дод. матеріал:
Last updated