Я не можу вказати на хороший ресурс в Інтернеті (статті англійської Вікіпедії на ці теми, як правило, є незмінними), але я можу підсумувати лекцію, яку я почув, і яка охоплювала базову теорію тестування.
Режими тестування
Існують різні класи тестів, такі як одиничні тести або інтеграційні тести . Тест блоку стверджує, що когерентний фрагмент коду (функція, клас, модуль) прийнятий за власними роботами, як очікувалося, тоді як тест на інтеграцію передбачає, що кілька таких фрагментів правильно працюють разом.
Тестовий випадок - відоме середовище, в якому виконується фрагмент коду, наприклад, за допомогою конкретного тестового введення або знущанням над іншими класами. Потім поведінка коду порівнюється з очікуваною поведінкою, наприклад, конкретне повернене значення.
Тест може лише довести наявність помилки, ніколи відсутність усіх помилок. Тести ставлять верхню межу правильності програми.
Кодове покриття
Для визначення метрики покриття коду вихідний код можна перевести на графік потоку управління, де кожен вузол містить лінійний сегмент коду. Контроль протікає між цими вузлами лише в кінці кожного блоку і завжди є умовним (якщо умова, то goto вузол A, інакше goto вузол B). Графік має один початковий вузол і один кінцевий вузол.
- На цьому графіку охоплення оператора - це відношення всіх відвідуваних вузлів до всіх вузлів. Повне покриття заяви недостатньо для ретельного тестування.
- Покриття гілки - це відношення всіх відвідуваних ребер між вузлами в CFG до всіх країв. Це недостатньо тестує петлі.
- Покриття шляху - це відношення всіх відвідуваних шляхів до всіх шляхів, де шлях - це будь-яка послідовність ребер від початку до кінця вузла. Проблема полягає в тому, що за допомогою циклів може існувати нескінченна кількість шляхів, тому повне покриття контуру неможливо практично перевірити.
Тому часто корисно перевіряти стан покриття .
- У простому охопленні умови кожна атомна умова є істинною, а один раз помилковою, але це не гарантує повного покриття оператора.
- При багаторазовому охопленні умов атомні умови приймають усі комбінації
true
та false
. Це передбачає повне покриття філій, але є досить дорогим. Програма може мати додаткові обмеження, які виключають певні комбінації. Ця методика хороша для отримання покриття відділеннями, може знайти мертвий код, але не може знайти помилки, що виникають із неправильного стану.
- У мінімальному множинному охопленні умови кожна атомна і складова умова є колись істинною і хибною. Це все ще передбачає повне охоплення філій. Це підмножина, що охоплює декілька умов, але потребує меншої кількості тестових випадків.
При побудові тестового входу з використанням покриття стану, тоді слід враховувати коротке замикання. Наприклад,
function foo(A, B) {
if (A && B) x()
else y()
}
потребує тестування з foo(false, whatever)
, foo(true, false)
та foo(true, true)
для повного мінімального множинного покриття умов.
Якщо у вас є об'єкти, які можуть бути в декількох станах, то тестування всіх переходів стану, аналогічних контрольним потокам, здається розумним.
Існує кілька більш складних показників покриття, але вони, як правило, схожі на показники, представлені тут.
Це методи тестування білого поля , і їх можна частково автоматизувати. Зауважте, що набір одиничних тестів повинен мати на меті забезпечити високе охоплення коду будь-яким обраним показником, але 100% не завжди можливо. Особливо важко перевірити поводження з винятками, коли несправності потрібно вводити в конкретні місця.
Функціональні тести
Потім є функціональні тести, які стверджують, що код дотримується специфікації, розглядаючи реалізацію як чорну скриньку. Такі тести корисні і для одиничних тестів, і для інтеграційних тестів. Оскільки неможливо протестувати всі можливі вхідні дані (наприклад, тестування довжини рядка з усіма можливими рядками), корисно згрупувати вхідні дані (та вихідні) в еквівалентні класи - якщо length("foo")
це правильно, foo("bar")
ймовірно, буде також працювати. Для кожної можливої комбінації між класами вхідної та вихідної еквівалентності вибирається і тестується щонайменше один репрезентативний вхід.
Слід додатково протестувати
- крайні випадки
length("")
, foo("x")
, length(longer_than_INT_MAX)
,
- значення, дозволені мовою, але не контрактом функції
length(null)
, і
- можливі непотрібні дані
length("null byte in \x00 the middle")
…
Що стосується числових даних, це означає тестування 0, ±1, ±x, MAX, MIN, ±∞, NaN
, а порівняння з плаваючою комою - тестування двох сусідніх плавців. Як ще одне доповнення, з класів еквівалентності можна обрати випадкові значення тесту. Щоб полегшити налагодження, варто записати використане насіння…
Нефункціональні тести: Навантажувальні тести, Стресові тести
Частина програмного забезпечення має нефункціональні вимоги, які також потрібно перевірити. До них відносяться тестування за визначеними межами (навантажувальні випробування) та поза ними (стрес-тести). Для комп’ютерної гри це може бути затвердженням мінімальної кількості кадрів в секунду в тесті навантаження. Веб-сайт може бути підданий стрес-тестуванню, щоб спостерігати час відгуку, коли вдвічі більше відвідувачів, ніж передбачалося, б'ють сервери. Такі тести актуальні не лише для цілих систем, але і для окремих об'єктів - як деградує хеш-таблиця з мільйоном записів?
Інші види тестів - це цілі системні тести, де моделюються сценарії, або тести прийняття для підтвердження того, що контракт на розробку був виконаний.
Нетестуючі методи
Відгуки
Існують методи не тестування, які можна використовувати для забезпечення якості. Прикладами є покрокові інструкції, офіційні огляди коду або парне програмування. Хоча деякі частини можуть бути автоматизовані (наприклад, за допомогою вкладишів), вони, як правило, займають багато часу. Однак огляди коду досвідченими програмістами мають високий рівень виявлення помилок і особливо цінні під час проектування, де автоматизоване тестування неможливо.
Коли огляди коду такі чудові, чому ми все ще пишемо тести? Великою перевагою тестових наборів є те, що вони можуть працювати (в основному) автоматично, і як такі дуже корисні для регресійних тестів .
Офіційна перевірка
Офіційна перевірка проходить і підтверджує певні властивості коду. Ручна перевірка здебільшого є життєздатною для критичних частин, рідше для цілих програм. Докази ставлять нижню межу правильності програми. Докази можуть бути автоматизовані до певної міри, наприклад, за допомогою перевірки статичного типу.
Деякі інваріанти можна явно перевірити, використовуючи assert
оператори.
Усі ці методи мають своє місце і є взаємодоповнюючими. TDD записує функціональні тести наперед, але тести можна судити за показниками їх покриття, коли код буде реалізований.
Введення тестового коду означає написання невеликих одиниць коду, які можна перевірити окремо (допоміжні функції з відповідною деталізацією, принцип єдиної відповідальності). Чим менше аргументів бере кожна функція, тим краще. Такий код також піддається введенню макетних об'єктів, наприклад, через введення залежності.
double pihole(double value) { return (value - Math.PI) / (value - Math.PI); }
що я дізнався у свого вчителя математики . У цьому коді є рівно одне отвір , яке неможливо виявити автоматично лише за допомогою тестування в чорному ящику. У Математиці немає такої діри. У підрахунку ви можете закрити отвір, якщо однобічні межі рівні.