Тестування на мутації (французькою: tests de mutation), я нещодавно знайшов цей термін, що описує процес, здатний виявляти прогалини в модульних тестах, виходячи за межі покриття коду. Сьогодні я представляю вам цей підхід, який полягає у проведенні цих тестів шляхом ручної обробки коду.

Модульні тести
Враховуючи, що корисність модульного тестування добре доведена, ця тема стає цікавою, якщо ви розробляєте перевірений проект, незалежно від того, наскільки важливим охоплення коду.
Модульні тести дозволяють виділити можливі регресії, викликані модифікацією коду. Теоретично, якщо тести підтверджують програму, це означає, що в додатку все працює правильно. Як перший і часто єдиний показник довіри, ми використовуємо покриття коду. Чим ближче цей показник наближається до 100%, тим більше він запевняє нас, що жодна регресія не проскочить через тріщини. На жаль, це твердження залишається теоретичним.
Хоча тести є важливими для підтвердження якісного застосування, їх важко продемонструвати чи навіть оцінити.
Покриття коду та охоплення справ
100% покриття коду не означає 100% перевіреного коду, а лише 100% цього коду, що виконується під час проходження тестів, не більше того.
Покриття коду (рядок, оператор, гілка тощо) вимірює лише те, який код був виконаний тестами, без гарантії виявлення дефектів. Він здатний лише ідентифікувати код, який ще не перевірений.
Тестування без затвердження є очевидним прикладом, оскільки, хоча код виконується, насправді не тестується. На щастя, цей сценарій залишається рідкісним, найчастіше зустрічається код, який частково перевірено набором тестів. Набір, який лише частково перевіряє код, який все ще може виконувати всі свої гілки.
У деяких випадках покриття коду не є показником захисту. Ось простий приклад:
function isAdult(user) {
return user.age >= 18;
}
Припустимо, ми хочемо перевірити вік користувача. Ми напишемо наступний код, щоб переконатися, що він є основним.
Щоб перевірити цей код, ми можемо спробувати використовувати 12 і 38 як вхідні дані. Цієї дії було б достатньо, щоб покрити цей код на 100%.
Результат був би таким самим, якби ми не розглядали 18 як більшість із такою помилкою в нашому коді:
function isAdult(user) {
return user.age > 18;
}
…або якщо ми перевірили значення лише 12 років, або ще гірше, якщо ми пропустили твердження в нашому тесті.
Тест на мутацію насправді зможе виявити, чи кожне твердження перевірено змістовно. Це стандартна міра для всіх інших видів покриття.
Інші питання в кодексі
Давайте тоді припустимо, що нам не потрібен непотрібний код у нашій програмі. Дійсно, кожна неперевірена частина буде джерелом потенційної помилки або навіть додаткової складності, якщо вона не є суттєвою.
Ось чому тестування на мутації є чудовим способом перевірити релевантність такого коду:
якщо (someVariable !== null && someVariable.hasValue()) {}
Чи потрібно перевіряти значення «нулю”? Умова була додана за звичкою? Це може означати, що ми не впевнені у змінній»someVariable” і вимагатиме подальшого аналізу. Ми не можемо піти глибше, не усвідомлюючи цього. У цьому нам також допомагає тестування на мутації.
Тести на мутацію: що це таке?
Щоб виявити недоліки в наших модульних тестах, є рішення: тестування на мутації.

Ця техніка дає більше впевненості в наших тестах. Тестування на мутації - досить проста концепція. Його принцип полягає в неправильному поводженні з вихідним кодом, змінюючи його, щоб переконатися, що пов’язані тести відповідним чином провалилися. Недоліки (або мутації) автоматично закладаються в наш код, а потім запускаються тести. Якщо тести невдалі, то мутація знищена. Якщо тести пройшли, значить, мутація вижила. У цьому випадку це означає, що тести не відповідають складності коду і залишають один або кілька його аспектів неперевіреними. Тоді якість наших тестів можна виміряти за відсотком вбитих мутацій.
Іншими словами, ми запускаємо модульні тести на автоматично змінених версіях коду. Коли код програми змінюється, він повинен давати інші результати і призводити до збою модульних тестів. Якщо модульний тест у цій ситуації не завершується, це може свідчити про збій у наборі тестів.
Ось кроки для досягнення цього:
- Запустіть звичайний набір тестів, щоб переконатися, що всі тести пройшли зелений колір.
- Змініть деякі частини перевіреного коду перед повторним запуском набору тестів.
- Переконайтеся, що після модифікації (мутації) тестованого коду тести завершилися невдало.
Повторюйте кроки 2 і 3, поки залишаються можливі мутації.
Давайте візьмемо конкретний приклад: уявіть мутанта як додатковий клас із лише однією зміною від початкового коду. Це може бути зміна логічного оператора в пропозиції if, як показано нижче:
if( a || b ) {…} => if( a && b ) {…}
Виявлення та відхилення такої модифікації існуючими тестами називають вбивством мутанта. З ідеальним набором тестів на місці жоден класовий мутант не вижив би. Але створення всіх можливих мутантів є ресурсомістким, тому неможливо досягти цього підходу вручну в реальних сценаріях.
На щастя, існують інструменти для створення мутантів на льоту та автоматичного виконання всіх тестів для кожного з них. Створення перетворення базується на наборі операторів мутації, покликаних виявити типові помилки програмування. Оператор мутації, який використовується для модифікації вищевказаного коду, називається оператором умови.

На практиці
Таким чином, ця техніка складається з двох частин: створення мутантів, а потім їх усунення.
Генерація мутантів — це етап генерації мутантних класів із вихідних класів. Для початку нам потрібен бізнес-код, за яким ми хочемо оцінити релевантність наших тестів. Тоді ми беремо а басейн можливих мутацій, причому мутація є модифікацією вихідного коду, як-от дія заміни одного оператора іншим.
Ось кілька прикладів.
- + стає –
- * СТАНЕ /
- >= стає ==
- істина стає хибною.
- видалення інструкції
- і т.п.
Ми можемо змінити арифметичний вираз e на |e| (ABS), змінити один реляційний арифметичний оператор на інший (ROR), змінити один арифметичний оператор на інший (AOR), змінити один булевий оператор на інший (COR), змінити булевий/арифметичний вираз, додавши − або ¬ ( UOI), змінити ім'я змінної іншим, змінити ім'я змінної константою того ж типу, змінити константу іншою константою того ж типу…
Фактичне генерування складається з проходження всіх інструкцій коду і для кожного визначення, чи застосовні мутації. Якщо так, кожна мутація призведе до появи нового мутанта.
Для наступної заяви:
if (a > 8) { x = y+1 }
Ми можемо розглядати наступних мутантів:
if (a < 8) { x = y+1 }
if (a ≥ 8) { x = y+1 }
if (a > 8) { x = y-1 }
if (a > 8) { x = y }
Цей процес може швидко стати ресурсомістким. Коли код, який потрібно змінити, містить велику кількість інструкцій і " басейн » можливих мутацій значний, то кількість згенерованих мутантів дуже швидко зростає.
Після завершення процесу генерації мутантів мутанти зберігаються до наступного кроку: елімінації!
Для другої частини процесу ми створили багато мутантів, які ми не хочемо проходити через тести; метою буде усунути якомога більше. Для цього нашою зброєю буде вдосконалення модульних тестів.
баланс
Для даного мутанта є два можливих результату: або тести завжди зелені, або принаймні один з них став червоним.

Зазвичай ми хочемо, щоб тести були зеленими. Але в цьому контексті ми шукаємо червоний колір. Справді, як ми бачили раніше, кожен мутант повинен провалити принаймні один модульний тест. Якщо хоча б один із тестів не пройшов, це доводить, що вони здатні виявити модифікації коду і, отже, запобігти можливим помилкам. З іншого боку, якщо всі тести залишаються зеленими, мутант виживає, тому він залишається невидимим для очей наших тестів.
Таким чином, мутант, який вижив, є ознакою пропущеного тесту!
Недоліки
Повний аналіз нашого коду може бути виснажливим. Як ми бачили, кількість мутантів може збільшуватися дуже швидко.
На першому етапі ми можемо, наприклад, створити 6000 мутантів. Під час другого етапу тестування більше 98% з них буде усунено, відсоток залежить від попередньої якості ваших тестів. У нас ще залишилося від 150 до 200 мутантів.
Ручний аналіз кожного з них займає багато часу. Більше того, наші модульні тести не відповідають виключно за їх виживання. Може з’явитися «еквівалентний мутант»: мутант, який змінює синтаксис вихідного коду, не змінюючи його семантики. Цей тип мутантів не дозволяє модульному тесту виявити його.
while(...) {
index++;
if (index == 10)
break;
}
Наприклад, мутація " == »До« <= дасть еквівалентний мутант. У цьому прикладі буде така ж умова виходу з циклу.
Попередній аналіз покриття коду, створення мутантів на льоту та всі необхідні тести забирають багато часу. Наприклад, код із 350 тестами збільшує час виконання на чотири в порівнянні зі звичайним запуском.
Враховуючи ці цифри та з практичних міркувань, тести на мутацію не можуть виконуватися так часто, як модульні тести. Тому важливо знайти відповідний робочий процес, який пропонує найкращий компроміс з точки зору ефективності. Для великих програмних систем це може означати, що тестування на мутації буде обмежено нічними запусками.
Перш ніж реалізувати їх, ви повинні використовувати передовий підхід до якості. Тести повинні бути розміщені в центрі розробки, щоб уникнути результатів, які є занадто об’ємними для аналізу. Однак, якщо охоплення коду досягло меж, це може бути хорошим підходом для експерименту. На жаль, нинішні інструменти здаються недостатньо промисловими.
Тестування мутацій і javascript
Тести мутації набагато краще відомі та використовуються у світі Java або PHP. Проте, починаючи з 2016 року, завдяки Stryker Mutator є спосіб виконувати тестування на мутації в JavaScript. Існує також Grunt-mutation-testing, більшість вихідного коду якого знаходиться в процесі міграції на Stryker.
Ось посилання на Github: http://stryker-mutator.github.io
Висновок
Ця стаття була швидким вступом до тестування на мутації. Ми розглянули тестові мутанти, оцінили прямий зв’язок між частотою мутантів і якістю існуючого набору тестів і спостерігали кореляцію з охопленням коду.
Оскільки покриття коду не є дуже надійним показником, Мутаційні тести – це швидкий і простий спосіб вимірювання надійності модульних тестів. Ми будемо просувати тести на мутацію там, де є справжня проблема: бізнес-код.
Загалом, тест на мутацію виглядає як приємне доповнення до набору інструментів забезпечення якості., на основі автоматизованих тестів. Ця практика з’явилася зовсім недавно в JavaScript і досі невідома. Буде цікаво почитати думки та відгуки досвідчених користувачів.
Орелі Амбал, (@Souvir) JS Майстриня @JS-Republic
[Actionbox колір = »за замовчуванням» назва = "" Опис = »На навчання JS-REPUBLIC посилається Datadock.
Знайдіть усі наші навчальні курси на нашому веб-сайті training.ux-republic.com:
- UX-дизайн
- Перевірений
- JavaScript
” btn_label=”Наші тренінги” btn_link=”http://training.ux-republic.com” btn_color=”primary” btn_size=”big” btn_icon=”star” btn_external=”1″]
