Тестване и дебъгване

** Самостоятелно тестване на функции. 
* Данните, с които ще се тества функцията, се получават по 3 начина: 
-- от входния поток (от клавиатура или от текстов файл с пренасочване на входния поток); 
-- като стойности, получени от цикъл; 
-- случайни числа. 
* Примери за тестване на функцията squareroot за намиране на квадратен корен по метода на Херон.
Първи пример - данните идват от входния поток:

// sqrtest1.cpp 
#include
<iostream>
#include <cmath>
using namespace std;

/**
   Tests whether two floating-point numbers are 
   approximately equal.
   @param x a floating-point number
   @param y another floating-point number
   @return true if x and y are approximately equal
*/
bool approx_equal(double x, double y)
{  const double EPSILON = 1E-14;
   if (x == 0) return fabs(y) <= EPSILON;
   if (y == 0) return fabs(x) <= EPSILON;
   return fabs(x - y) / max(fabs(x), fabs(y)) <= EPSILON;
}

/* Function to be tested */
/**
   Computes the square root using Heron's formula
   @param a an integer >= 0
   @return the square root of a
*/
double squareroot(double a)
{  if (a == 0) return 0;

   double xnew = a;
   double xold;
   do
   {  xold = xnew;
      xnew = (xold + a / xold) / 2;
   }
   while (!approx_equal(xnew, xold));
   return xnew;
}

/* Test harness */
int main()
{  double x;
   while (cin >> x)
   {  double y = squareroot(x);
      cout << "squareroot of " << x << " = " << y << "\n";
   }
   return 0;
}
25 
squareroot of 25 = 5 
3 
squareroot of 3 = 1.73205 
q

Втори пример - входните стойности на функцията се генерират от цикъл.

// sqrtest2.cpp 
/* Test harness */
int main()
{  double x;
   for (x = 0; x <= 10; x = x + 0.5)
   {  double y = squareroot(x);
      cout << "squareroot of " << x << " = " << y << "\n";
   }
   return 0;
}
squareroot of 0 = 0 
squareroot of 0.5 = 0.707107 
squareroot of 1 = 1 
squareroot of 1.5 = 1.22474 
squareroot of 2 = 1.41421

Трети пример - входните стойности се получават от генератор за случайни числа. 

// sqrtest3.cpp 
#include
<iostream>
#include <cstdlib>
#include <cmath>
#include <ctime>
using namespace std;

/**
   Sets the seed of the random number generator.
*/
void rand_seed()
{  int seed = static_cast<int>(time(0));
   srand(seed);
}

/** 
   Compute a random floating point number in a range
   @param a the bottom of the range
   @param b the top of the range
   @return a random floating point number x, 
   a <= x and x <= b
*/
double rand_double(double a, double b)
{  return a + (b - a) * rand() * (1.0 / RAND_MAX);
}

/**
   Tests whether two floating-point numbers are 
   approximately equal.
   @param x a floating-point number
   @param y another floating-point number
   @return true if x and y are approximately equal
*/
bool approx_equal(double x, double y)
{  const double EPSILON = 1E-14;
   if (x == 0) return fabs(y) <= EPSILON;
   if (y == 0) return fabs(x) <= EPSILON;
   return fabs(x - y) / max(fabs(x), fabs(y)) <= EPSILON;
}

/* Function to be tested */
/**
   Computes the square root using Heron's formula
   @param a an integer >= 0
   @return the square root of a
*/
double squareroot(double a)
{  if (a == 0) return 0;

   double xnew = a;
   double xold;
   do
   {  xold = xnew;
      xnew = (xold + a / xold) / 2;
   }
   while (!approx_equal(xnew, xold));
   return xnew;
}

/* Test harness */
int main()
{  rand_seed();
   int i;
   for (i = 1; i <= 100; i++)
   {  double x = rand_double(0, 1E6);
      double y = squareroot(x);
      cout << "squareroot of " << x << " = " << y << "\n";
   }
   return 0;
}
squareroot of 185949 = 431.218 
squareroot of 680715 = 825.055 
squareroot of 17883.8 = 133.73 
squareroot of 238868 = 488.742

** Подбор на тестови примери. 
1. Докато се пише програмата, трябва да имаме прост тестов пример, на който знаем решението. 
2. Програмата се проверява с други тестови примери, също с известни решения - позитивни тестове. 
3. Включват се и граничните случаи.
Например за функцията squareroot това са 0, големи числа (напр. 1Е20) и числа, близки до 0 (напр. 1е-20).
Целта е да се определят границите на параметрите, за които функцията работи вярно. 
4. Функцията се проверява с негативни тестови примери - некоректни входни данни. Такива за squareroot са отрицателни стойности на параметъра.
Използване на файл за запазване на тестовия вход и изпълнение с пренасочване на входния и изходния потоци.

>sqrtest1 < test.in > test.out



** Оценка на резултатите от тестването. 
* Как можем да оценим дали върнатата стойност от функцията е вярна: 
- като предварително знаем решението; 
- с програма за проверка на върнатите стойности; 
- с "оракул" - друга функция, която решава същата задача (може по-бавно и неефективно!). 
* В нашия случай можем да сравним квадрата на върнатото число от функцията с входната стойност.
Ето тестващото гнездо: 

// sqrtest4.cpp
/* Test harness */
int main()
{  int i;
   for (i = 1; i <= 100; i++)
   {  double x = rand_double(0, 1E6);
      double y = squareroot(x);
      if (not_equal(y * y, x)) 
         cout << "Test failed. ";
      else 
         cout << "Test passed. ";
      cout << "squareroot of " << x << " = " << y << "\n";
   }
   return 0;
}
*  За оракул можем да използваме стандартната аритметична функция pow(x,0.5)или функцията sqrt(x). 
// sqrtest5.cpp
/* Тестващо гнездо */
 
int main() 
{ 
   rand_seed(); 
   int i; 
   for (i = 1; i <= 100; i++) 
   {  double x = rand_double(0, 1E6); 
      double y = squareroot(x); 
      if (not_equal(y, pow(x, 0.5))) cout << "Test failed. "; 
      else                           cout << "Test passed. "; 
      cout << "squareroot of " << x << " = " << y << "\n"; 
   } 
   return 0; 
}

** Макрос assert
Функциите често съдържат неявни предположения - напр. знаменатели трябва да са различни от нула, заплатите не трябва да бъдат отрицателни и т.н.
Такива "незаконни" стойности могат да се вмъкнат в програмата от входа или в резултат на предишна грешка.

Какво трябва да се направи, когато дадена функция е извикана с неподходяща стойност (напр sqrt(-1))?
Най-бруталният начин е отпечатване на съобщение и прекратяване на цялата програма.
C++ поддържа сложен механизъм, наречен обработка на изключения.
Когато е възможно е желателно да се избегне прекъсване на програмата (въпреки, че е трудно).

Макрос assert осигурява ценна проверка на "здрав разум".

Пример:
void raise_salary(Employee& e, double by)
{ assert(e.get_salary() >= 0 );
assert(by >= -100);
double new_salary = e.get_salary() * (1 + by / 100);
e.set_salary(new_salary);
}
Ако условието не е изпълнено, програмата завършва с полезно съобщение за грешка и показва номера на реда в текста на програмата.
assertion failed in file finclac.cpp line 61: by >= -100
Това е сигнал, че нещо се е объркало другаде и че програмата се нуждае от по-нататъшно тестване.
За да се използва макроса, трябва да се добави #include<cassert>.

** Трасиране на програмата
За да се получи разпечатка за работата на програмата, вмъкнете съобщения (контролни печати) в началото и в края на всяка функция.
Полезно е отпечатване на входните параметри, когато се влиза във функцията и отпечатване на стойността за връщане, когато се излиза от функцията.
string int_name(int n)
{ cout << "Entering digit_name. n = " << n << "\n";
...
cout << "Exiting digit name. Return value = "
<< s << "\n";
return s;
}
[trace.cpp]
За вмъкване на подходящи контролни печати се иска доста врене.
Ако са добавени твърде много съобщения, ще се получи лавина от печат, който трудно се анализира.
Ако са добавени твърде малко съобщения, може да не се получи достатъчно информация къде е грешката.
Когато завършите програмата, всички контролни печати трябва да бъдат изтрити.
Ако отново се появи грешка, контролните печати трябва да се пишат отново.

Често професионалните програмисти използват дебъгер за откриване на грешки в текста на програмата.

** Дебъгер
Кратко практическо ръководство за работа с GDB

CITB102