Тема. Основні поняття тестування

План

1. Концепція тестування.

2. Організація тестування.

3. Фази тестування. Керуючий граф програми.

4. Основні проблеми тестування

 

1. Концепція тестування

Програма — це аналог формули у звичайній математиці.

Формула для функції f, отриманою суперпозицією функцій f1, f2, ... fn — вираз, що описує цю суперпозицію.

f = f1* f2* f3*... * fn

Якщо аналог f1, f2,... fn — оператори мови програмування, то їхня формула — програма.

Існує два методи обґрунтування істинності формул:

Формальний підхід або доказ застосовується, коли з вихідних формул—аксіом за допомогою формальних процедур (правил виводу) виводяться шукані формули й твердження (теореми). Вивід здійснюється шляхом переходу від одних формул до інших за строгими правилами, які дозволяють звести процедуру переходу від формули до формули до послідовності текстових підстановок:

A**3 = A*A*A

A*A*A = A —> R, A*R —> R, A*R —> R

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

Інтерпретаційний підхід застосовується, коли здійснюється підстановка констант у формули, а потім інтерпретація формул як осмислених тверджень в елементах множині конкретних значень. Істинність формул, що інтерпретуються, перевіряється на кінцевих множинах можливих значень. Складність підходу полягає в тому, що на кінцевих множинах комбінації можливих значень для реалізації вичерпної перевірки можуть виявитися досить великі.

Інтерпретаційний підхід використається під час експериментальної перевірки відповідності програми своєї специфікації.

Застосування інтерпретаційного підходу у формі експериментів над програмою, що виконується, становить суть налагодження й тестування.

Основна термінологія

Налагодження (debug, debugging) — процес пошуку, локалізації й виправлення помилок у програмі [IEEE Std.610—12.1990].

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

Тестування забезпечує виявлення (констатацію наявності) фактів розбіжностей з вимогами (помилок).

Як правило, на фазі тестування здійснюється й виправлення ідентифікованих помилок, що включає локалізацію помилок, знаходження причин помилок і відповідне коректування програми тестованого додатка (Application Under Testing (AUT) або Implementation Under Testing (IUT)).

Якщо програма не містить синтаксичних помилок (пройшла трансляцію) і може бути виконана на комп’ютері, вона обов’язково обчислює яку-небудь функцію, що здійснює відображення вхідних даних у вихідні. Це означає, що комп’ютер на своїх ресурсах довизначає частково визначену програмою функцію до тотальної визначеності. Отже, судити про правильність або неправильність результатів виконання програми можна, тільки порівнюючи специфікацію бажаної функції з результатами її обчислення, що й здійснюється в процесі тестування.

Приклад пошуку й виправлення помилки

Налагодження забезпечує локалізацію помилок, пошук причин помилок і відповідне коректування програми (Приклад 1, Приклад 2).

// Метод обчислює негативну ступінь n числа x

double Power(double x, int n)

{

double z=1;

int i;

for(i=1;n>=i;i++)

{

z=z*x;

}

return z;

}

Приклад 1. Вихідний текст методу Power

Якщо викликати метод Power з негативним значенням ступеня n Power(2,—1), то одержимо некоректний результат —2. Виправимо метод так, щоб помилкове значення параметра (неприпустиме за специфікацією значення) ідентифікувалося спеціальним повідомленням, а повернений результат дорівнював 1 (Приклад 2).

//  Метод обчислює негативну ступінь n числа x

double PowerNonNeg(double x, int n)

{

double z=1;

int i;

if (n>0)

{

for (i=1;n>=i;i++)

{

z = z*x;

}

}

else printf("Помилка! Ступінь числа n має бути більше 0.\n");

return z;

}

Приклад 2. Скоректований вихідний текст

Якщо викликати скоректований метод PowerNonNeg(2,—1) з негативним значенням параметра ступеня, то повідомлення про помилку буде видано автоматично.

Тестування розділяють на статичне й динамічне:

Статичне тестування виявляє формальними методами аналізу, без виконання тестованої програми, невірні конструкції або невірні відносини об’єктів програми (помилки формального завдання) за допомогою спеціальних інструментів контролю коду — CodeChecker.

Динамічне тестування (власне тестування) здійснює виявлення помилок тільки в програмі, що виконується, за допомогою спеціальних інструментів автоматизації тестування — Testbed або Testbench.

 

 

2.   Організація тестування

Тестування здійснюється на заданій заздалегідь множині вхідних даних X і множині передбачуваних результатів Y — (X, Y), які задають графік бажаної функції. Крім того, зафіксована процедура Оракул (oracle), що визначає, чи відповідають вихідні дані — Yв (обчислені за вхідним даними — X) бажаним результатам — Y, тобто чи належить кожна обчислена точка (x, yв) графікові бажаної функції (X, Y).

Оракул дає висновок про факт появи неправильної пари (x, yв) і нічого не говорить про те, яким чином вона була обчислена або який правильний алгоритм — він тільки порівнює обчислені й бажані результати. Оракулом може бути навіть замовник або програміст, що робить відповідні обчислення в розумі, оскільки Оракулові потрібний який-небудь альтернативний спосіб одержання функції (X, Y) для обчислення еталонних значень Y.

Прикладом може служити порівняння словесного опису пункту специфікації з результатом виконання фрагмента коду.

Пункт специфікації: "Метод Power повинен приймати вхідні параметри: x — ціле число, як підноситься до в ступіню, і n — негативний порядок ступеня. Метод повинен повертати обчислене значення xn ".

Виконуємо метод з наступними параметрами: Power(2,2).

Перевірка результату виконання можлива, коли результат обчислення заздалегідь відомий — 4. Якщо результат виконання 22 = 4, то він відповідає специфікації.

У процесі тестування Оракул послідовно одержує елементи множині (X, Y) і відповідні їм результати обчислень (X,Yв) для ідентифікації фактів розбіжностей (test incident).

При виявленні  запускається процедура виправлення помилки, що полягає в уважному аналізі (перегляді) протоколу проміжних обчислень, які призвели до (x, yв), за допомогою наступних методів:

ü  "Виконання програми в розумі" (deskchecking).

ü  Вставка операторів протоколювання (друкування) проміжних результатів (logging).

ü  Приклад вставки операторів протоколювання проміжних результатів.

Можна виводити проміжні значення змінних під час виконання програми. Код, що здійснює вивід, розташований нижче (Приклад 3). Цей метод відноситься до найбільш популярних засобів автоматизації налагодження програмістів минулих десятиліть. У цей час він відомий як метод впровадження "агентів" у текст програми, що відладжується.

//  Метод обчислює негативну ступінь n числа x

double Power(double x, int n)

{

double z=1;

int i;

for (i=1;n>=i;i++)

{

z = z*x;

printf("i = %d z = %f\n", i, z);

}

return z;

}

Приклад 3. Вихідний текст методу Power із вставкою оператора протоколювання

v Покрокове виконання програми (single—step running). Під час покрокового виконання програми код виконується рядок за рядком. Покрокове виконання дотепер є потужним методом автономного тестування й налагодження невеликих програм.

v Виконання із замовленими зупинками (breakpoints), аналізом трас (traces) або станів пам’яті — дампів (dump).

Контрольна точка (breakpoint) — точка програми, яке у разі її досягнення посилає відладчику сигнал. За цим сигналом або тимчасово припиняється виконання програми, або запускається програма "агент", що фіксує стан заздалегідь визначених змінних або областей у цей момент.

Коли виконання в контрольній точці припиняється, програма переходить у режим «зупинки» (break mode). Вхід у режим «зупинки» не перериває й не закінчує виконання програми й дозволяє аналізувати стан окремих змінних або структур даних. Повернення з режиму break mode у режим виконання може відбутися в будь-який момент за бажанням користувача.

Коли в контрольній точці викликається програма "агент", вона теж припиняє виконання програми, але тільки на час, необхідний для фіксації стану обраних змінних або структур даних у спеціальному електронному журналі — Log—файлі, після чого відбувається автоматичне повернення в режим виконання.

Траса — це "збережений шлях " на керуючому графі програми, тобто зафіксовані в журналі записи про стани змінних у заданих точках в ході виконання програми.

Наприклад: на Мал. 4 умовно зображений керуючий граф деякої програми. Траса, що проходить через вершини 0—1—3—4—5 зафіксована в Табл.1. Рядки таблиці відображають вершини керуючого графа програми або breakpoints, у яких фіксувалися поточні значення замовлених користувачем змінних.

Мал. 4. Керуючий граф програми

 

Таблиця 1. Траса, що проходить через вершини 0—1—3—4—5

№ вершини

Значення x

Значення z

Значення n

Значення i

0

3

1

2

не зафіксовано

1

3

1

2

не зафіксовано

3

3

1

2

1

4

3

3

2

2

5

3

3

2

не зафіксовано

 

Дамп — область пам’яті, стан якої фіксується в контрольній точці у вигляді єдиного масиву або декількох зв’язаних масивів. Під час  аналізу, що здійснюється після виконання траси в режимі off-line, стани дампа структуруються, і виділені області або поля порівнюються зі станами, передбаченими специфікацією.

Реверсивне (зворотне) виконання (reversible execution).

Зворотне виконання програми можливе за умови збереження на кожному кроці програми всіх значень змінних або станів програми для відповідної траси. Тоді піднімаючись від кінцевої точки траси до будь-якиї іншої, можна по кроках зробити обчислення станів, рухаючись від наслідку до причини, від станів на виході перетворювача даних до станів на його вході. Природно, такі можливості ми одержуємо в режимі off-line з аналізу під час фіксації в Log — файлі всієї історії виконання траси.

Приклад зворотного виконання для програми обчислення ступеня числа x

У програмі на Прикладі 4 фіксуються значення всіх змінних після виконання кожного оператора.

//  Метод обчислює негативну ступінь n числа x

double PowerNonNeg(double x, int n)

{

double z=1;

int i;

printf("x=%f z=%f n=%d\n",x,z,n);

if (n>0)

{

printf("x=%f z=%f n=%d\n",x,z,n);

for (i=1;n>=i;i++)

{

z = z*x;

printf("x=%f z=%f n=%d i=%d\n",x,z,n,i);

}

}

else printf("Помилка! Ступінь числа n повинна бути більше 0.\n");

return z;

}

 Приклад 4. Вихідний код з фіксацією результатів виконання операторів.

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

Отже, у процесі тестування порівняння проміжних результатів з отриманими незалежно еталонними результатами дозволяє знайти причини й місце помилки, виправити текст програми, провести повторну трансляцію й настроювання на виконання й продовжити тестування.

Тестування закінчується, коли виконалося або "пройшло" (pass) успішно достатню кількість тестів відповідно до обраного критерію тестування.

Наскрізний приклад тестування

Візьмемо програму, що трохи відрізняється від Прикладу 4:

//  Метод обчислює ступінь n числа x

#include <stdio.h>

double Power(int x, int n)

{

int z=1;

int i;

for (i=1;n>=i;i++)

{

z = z*x;

}

return z;

}

 

void main(void)

{

int x;

int n;

printf("Enter x:");

if(scanf("%d",&x))

{

if ((x>=0) & (x<=999))

{

printf("Enter n:");

if(scanf("%d",&n)) {

if ((n>=1) & (n<=100))

{

printf("The power n of x is %f\n", Power(x,n));

}

else

{

printf("Error : n must be in [1..100]\n");

}

}

else

{

printf("Error : Please enter a numeric argument\n");

}

}

else

{

printf("Error : x must be in [0..999]\n");

}

}

else

{

printf("Error : Please enter a numeric argument\n");

}

}

Приклад 5. Інший приклад обчислення ступеня числа

Для наведеної програми, що обчислює ступінь числа (Приклад 5), відтворимо послідовність дій, необхідних для тестування.

Специфікація програми

Ø  На вхід програма приймає два параметри: x — число, n — ступінь. Результат обчислення виводиться на консоль.

Ø  Значення числа й ступені повинні бути цілими.

Ø  Значення числа, що підноситься в ступінь, повинні лежати в діапазоні — [0..999].

Ø  Значення ступеня повинні знаходяться в діапазоні — [1..100].

Ø  Якщо числа, що подаються на вхід, лежать за межами зазначених діапазонів, то повинне видаватися повідомлення про помилку.

Розробка тестів

Ø   Визначимо області еквівалентності вхідних параметрів.

Ø   Для x — числа, що підноситься в ступінь, визначимо класи можливих значень:

o  x < 0 (помилкове)

o  x > 999 (помилкове)

o  x — не число (помилкове)

o  0 <= x <= 999 (коректне)

Ø   Для n — ступеня числа:

o  n < 1 (помилкове)

o  n > 100 (помилкове)

o  n — не число (помилкове)

o  1 <= n <= 100 (коректне)

Аналіз тестових випадків

Ø  Вхідні значення: (x = 2, n = 3) (покривають класи 4, 8).

Ø  Очікуваний результат: The power n of x is 8.

Ø  Вхідні значення: {(x = —1, n = 2), (x = 1000, n = 5)} (покривають класи 1, 2).

Ø  Очікуваний результат: Error : x must be in [0..999].

Ø  Вхідні значення: {(x = 100, n = 0), (x = 100, n = 200)} (покривають класи 5,6).

Ø  Очікуваний результат: Error : n must be in [1..100].

Ø  Вхідні значення: (x = ADS n = ASD) (покривають класи еквівалентності 3, 7).

Ø  Очікуваний результат: Error : Please enter a numeric argument.

Перевірка на граничні значення:

Ø Вхідні значення: (x = 999 n = 1).

Ø Очікуваний результат: The power n of x is 999.

Ø Вхідні значення: x = 0 n = 100.

Ø Очікуваний результат: The power n of x is 0.

Оцінка результатів виконання програми на тестах

У процесі тестування Оракул послідовно одержує елементи множині (X, Y) і відповідні їм результати обчислень YВ. У процесі тестування виробляється оцінка результатів виконання шляхом порівняння одержуваного результату з очікуваним.

 

3. Фази тестування

Реалізація тестування розділяється на три етапи:

Створення тестового набору (test suite) шляхом ручної розробки або автоматичної генерації для конкретного середовища тестування (testing environment).

Прогін програми на тестах, керований тестовим монітором (test monitor, test driver [IEEE Std 829—1983]) з одержанням протоколу результатів тестування (test log).

Оцінка результатів виконання програми на наборі тестів з метою ухвалення рішення щодо продовження або зупинки тестування.

Основна проблема тестування — визначення достатності множині тестів для істинності виводу про правильність реалізації програми, а також знаходження множині тестів, що володіє цією властивістю.

Простий приклад

Розглянемо питання тестування на прикладі простої програми (Приклад 6) мовою С. Текст цієї програми й деяких інших трохи видозмінений з метою зробити ілюстрацію описуваних фактів більше прозорою.

// Функція обчислює негативну ступінь n числа x

double Power(double x, int n){

double z=1; int i;

for (i=1;n>=i;i++)

{z = z*x;};

return z;}

Приклад 6. Приклад простої програми мовою С

Мал. 5. Керуючий граф програми

Керуючий граф програми (КГП) на мал. 5 відображає потік керування програми. Нумерація вузлів графа відповідає кожній з дій програми.

Керуючий граф програми

Керуючий граф програми (КГП) — граф G (V,A), де V (V1,... Vm) — безліч вершин (операторів), A (A1,... An) — безліч дуг (керувань), що з’єднують оператори-вершини.

Шлях — послідовність вершин і дуг КГП, у якій будь-яка дуга виходить із вершини Vi і приходить у вершину Vj, наприклад: (3, 4, 7), (3, 4, 5, 6, 4, 5, 6), (3, 4), (3, 4, 5, 6).

Гілка — шлях  (V1, V2, ... Vk), де V1 — або перший, або умовний оператор програми, Vk — або умовний оператор, або оператор виходу із програми, а всі інші оператори — безумовні, наприклад: (3,4) (4,5,6,4) (4,7). Шляхи, що розрізняються хоча б числом проходжень циклу — різні шляхи, тому число шляхів у програмі може бути не обмежене. Гілки — лінійні ділянки програми, їх кількість скінченна.

Існують реалізовані й нереалізовані шляхи в програмі, у нереалізовані шляхи у звичайних умовах потрапити не можна.

float H(float x,float y)

{

float H;

1  if (x*x+y*y+2<=0)

2  H = 17;

3  else H = 64;

4  return H*H+x*x;

}

Приклад 7. Приклад опису функції з реалізованими й нереалізованими шляхами

Наприклад, для функції Приклад 7 шлях (1, 3, 4) реалізуємо, шлях (1, 2, 4) не реалізуємо в умовах нормальної роботи. Але при збоях навіть нереалізований шлях може реалізуватися.

 

4. Основні проблеми тестування

Розглянемо два приклади тестування:

Нехай програма H(x:int, y:int) реалізована в машині з 64 розрядними словами, тоді потужність множині тестів ||(X,Y)||=2**64.

Це означає, що комп’ютеру, який працює на частоті 1Ггц, для прогону цього набору тестів (за умови, що один тест виконується за 100 команд) буде потрібно ~ 3K років.

На мал. 6 наведено фрагмент схеми програми керування схватом робота, де інтервал між моментами спрацьовування схвата невизначений.

Цей тривіальний приклад вимагає прогону нескінченної множині послідовностей вхідних значень із різними інтервалами спрацьовування схвата (Приклад 8).

#include <stdio.h>

/* Прочитати значення датчика */

int ReadSensor(int Sensor)

{

/* ...читання значення датчика */

printf("...reading sensor value\n");

return Sensor;

}

/* Відкрити схват */

void OpenHand()

{

/* ...відкриваємо схват */

printf("...opening hand\n");

}

/* Закрити схват */

void CloseHand()

{

/* ...закриваємо схват */

printf("...closing hand\n");

}

void main(void)

{

int s;

while (1)

{

printf("Enter Sensor value (0/1)");

scanf("%d",&s);

if (ReadSensor(s))

{

OpenHand();

CloseHand();

}

}

}

Приклад 8. Фрагмент програми спрацьовування схвата

Мал. 6. Тестова послідовність сигналів датчика схвата

Звідси вивід:

v Тестування програми на всіх вхідних значеннях неможливо.

v Неможливе тестування й на всіх шляхах.

Отже, треба відбирати кінцевий набір тестів, що дозволяє перевірити програму на основі наших інтуїтивних даних.

Вимога до тестів — програма на кожному з них повинна зупинятися, тобто не зациклюватися. Чи можна заздалегідь гарантувати зупинку на будь-якому тесті?

У теорії алгоритмів доведено, що не існує загального методу для рішення цього питання, а також питання, чи досягне програма на даному тесті заздалегідь фіксованого оператора.

Завдання про вибір кінцевого набору тестів (X, Y) для перевірки програми в загальному випадку нерозв’язні.

Тому для рішення практичних завдань залишається шукати окремі випадки рішення цього завдання.

 

Питання для самоконтролю:

1.     Яку аналогію можна провести між формулою та програмним кодом?

2.     В чому полягає інтерпретаційний підхід?

3.     В чому незручність використання методу?

4.     Що таке налагодження ПЗ?

5.     Що таке процес трансляції програми?

6.     Дайте визначення статичного тестування.

7.     Дайте визначення динамічного тестування.

8.     Хто такий або що таке оракул?

9.     Які методи протоколювання проміжних обчислень ви можете назвати?

10. Що таке контрольна точка?

11. Дайте визначення траси. Дайте визначення дампа.

12. Перерахуйте та коротко охарактеризуйте фази тестування.

13. В чому полягає основна проблема тестування?

14. Дайте визначення керуючого графу програми.

15. Дайте визначення шляху, гілки-шляху.