![]() |
|||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Основные понятия тестирования Концепция тестирования
Преимущество формального подхода заключается в том, что с его помощью удается избегать обращений к бесконечной области значений и на каждом шаге доказательства оперировать только конечным множеством символов.
Интерпретационный подход используется при экспериментальной проверке соответствия программы своей спецификации
|
|||||||||||||||||||||||||||||||||||||
Таблица 2.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 анализа при фиксации в Log – файле всей истории выполнения трассы.
Пример обратного выполнения для программы вычисления степени числа x
В программе нафиксируются значения всех переменных после выполнения каждого оператора.
// Метод вычисляет неотрицательную
// степень n числа x
static public double PowerNonNeg(double x,
int n)
{
double z=1;
Console.WriteLine("x={0} z={1} n={2}",
x,z,n);
if (n>0)
{
Console.WriteLine("x={0} z={1} n={2}",
x,z,n);
for (int i=1;n>=i;i++)
{
z = z*x;
Console.WriteLine(
"x={0} z={1} n={2}" +
" i={3}",x,z,n,i);
}
}
else Console.WriteLine(
"Ошибка ! Степень" +
" числа n должна быть больше 0.");
return z;
}
Пример 2.4. Исходный код с фиксацией результатов выполнения операторов
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;
}
Пример 2.4.1. Исходный код с фиксацией результатов выполнения операторов
Зная структуру управляющего графа программы и имея значения всех переменных после выполнения каждого оператора, можно осуществить обратное выполнение (например, в уме), подставляя значения переменных в операторы и двигаясь снизу вверх, начиная с последнего.
Итак, в процессе тестирования сравнение промежуточных результатов с полученными независимо эталонными результатами позволяет найти причины и место ошибки, исправить текст программы, провести повторную трансляцию и настройку на выполнение и продолжить тестирование.
Тестирование заканчивается, когда выполнилось или "прошло" (pass) успешно достаточное количество тестов в соответствии с выбранным критерием тестирования.
Тестирование – это:
The process of operating a system or component under specified conditions, observing or recording the results, and making an evaluation of some aspect of the system or component.
The process of analyzing a software item to detect the differences between existing and required conditions (that is, bugs) and to evaluate features of software items [[IEEE Std.610-12.1990],.
Сквозной пример тестирования
Возьмем несколько отличающуюся отпрограмму:
// Метод вычисляет степень n числа x
static public double Power(int x, int n)
{
int z=1;
for (int i=1;n>=i;i++)
{
z = z*x;
}
return z;
}
[STAThread]
static void Main(string[] args)
{
int x;
int n;
try
{
Console.WriteLine("Enter x:");
x=Convert.ToInt32(Console.ReadLine());
if ((x>=0) & (x<=999))
{
Console.WriteLine("Enter n:");
n=Convert.ToInt32(Console.ReadLine());
if ((n>=1) & (n<=100))
{
Console.WriteLine("The power n" + " of x is {0}", Power(x,n));
Console.ReadLine();
}
else
{
Console.WriteLine("Error : n " + "must be in [1..100]");
Console.ReadLine();
}
}
else
{
Console.WriteLine("Error : x " + "must be in [0..999]");
Console.ReadLine();
}
}
catch (Exception e)
{
Console.WriteLine("Error : Please enter " + "a numeric argument.");
Console.ReadLine();
}
}
Пример 2.5. Другой пример вычисления степени числа
#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");
}
}
Пример 2.5.1. Другой пример вычисления степени числа
Для приведенной программы, вычисляющей степень числа (), воспроизведем последовательность действий, необходимых для тестирования.
Спецификация программы
На вход программа принимает два параметра: x - число, n – степень. Результат вычисления выводится на консоль.
Значения числа и степени должны быть целыми.
Значения числа, возводимого в степень, должны лежать в диапазоне – [0..999].
Значения степени должны лежать в диапазоне – [1..100].
Если числа, подаваемые на вход, лежат за пределами указанных диапазонов, то должно выдаваться сообщение об ошибке.
Разработка тестов
Определим области эквивалентности входных параметров.
Для x – числа, возводимого в степень, определим классы возможных значений:
Для n – степени числа:
Анализ тестовых случаев
Ожидаемый результат: The power n of x is 8.
Ожидаемый результат: Error : x must be in [0..999].
Ожидаемый результат: Error : n must be in [1..100].
Ожидаемый результат: Error : Please enter a numeric argument.
Ожидаемый результат: The power n of x is 999.
Ожидаемый результат: The power n of x is 0.
Выполнение тестовых случаев
Запустим программу с заданными значениями аргументов.
Оценка результатов выполнения программы на тестах
В процессе тестирования Оракул последовательно получает элементы множества (X,Y) и соответствующие им результаты вычислений YВ. В процессе тестирования производится оценка результатов выполнения путем сравнения получаемого результата с ожидаемым.
Реализация тестирования разделяется на три этапа:
Основная проблема тестирования - определение достаточности множества тестов для истинности вывода о правильности реализации программы, а также нахождения множества тестов, обладающего этим свойством.
Простой пример
Рассмотрим вопросы тестирования на примере простой программы на языке С#. Текст этой программы и некоторых других несколько видоизменен с целью сделать иллюстрацию описываемых фактов более прозрачной.
/* Функция вычисляет неотрицательную
степень n числа x */
1 double Power(double x, int n){
2 double z=1; int i;
3 for (i=1;
4 n>=i;
5 i++)
6 {z = z*x;} /* Возврат в п.4 */
7 return z;}
Пример 2.6. Пример простой программы на языке С#
/* Функция вычисляет неотрицательную
степень n числа x */
1 double Power(double x, int n){
2 double z=1; int i;
3 for (i=1;
4 n>=i;
5 i++)
6 {z = z*x;} /* Возврат в п.4 */
7 return z;}
Пример 2.6.1. Пример простой программы на языке С
Управляющий граф программы (УГП) на отображает поток управления программы. Нумерация узлов графа совпадает с нумерацией строк программы. Узлы 1 и 2 не включаются в УГП, поскольку отображают строки описаний, т.е. не содержат управляющих операторов.
Управляющий граф программы
Управляющий граф программы (УГП)– граф 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). Пути, различающиеся хотя бы числом прохождений цикла – разные пути, поэтому число путей в программе может быть не ограничено. Ветви - линейные участки программы, их конечноe число.
Существуют реализуемые и нереализуемые пути в программе, в нереализуемые пути в обычных условиях попасть нельзя.
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;
}
Пример 2.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;
}
Пример 2.7.1. Пример описания функции с реализуемыми и нереализуемыми путями
Например, для функции путь(1,3,4) реализуем, путь(1,2,4) нереализуем в условиях нормальной работы. Но при сбоях даже нереализуемый путь может реализоваться.
Основные проблемы тестирования
Рассмотрим два примера тестирования:
Это означает, что компьютеру, работающему на частоте 1Ггц, для прогона этого набора тестов (при условии, что один тест выполняется за 100 команд) потребуется ~ 3K лет.
Этот тривиальный пример требует прогона бесконечного множества последовательностей входных значений с разными интервалами срабатывания схвата .
// Прочитать значения датчика
static public bool ReadSensor(bool Sensor)
{
//...чтение значения датчика
Console.WriteLine("...reading sensor value");
return Sensor;
}
// Открыть схват
static public void OpenHand()
{
//...открываем схват
Console.WriteLine("...opening hand");
}
// Закрыть схват
static public void CloseHand()
{
//...закрываем схват
Console.WriteLine("...closing hand");
}
[STAThread]
static void Main(string[] args)
{
while (true)
{
Console.WriteLine("Enter Sensor value (true/false)");
if (ReadSensor(Convert.ToBoolean(Console.ReadLine())))
{
OpenHand();
CloseHand();
}
}
}
Пример 2.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();
}
}
}
Пример 2.8.1. Фрагмент программы срабатывания схвата
Отсюда вывод:
Требование к тестам - программа на любом из них должна останавливаться, т.е. не зацикливаться. Можно ли заранее гарантировать останов на любом тесте?
Задача о выборе конечного набора тестов(X,Y) для проверки программы в общем случае неразрешима.
Поэтому для решения практических задач остается искать частные случаи решения этой задачи.