Kapitel 7
Fejl, Debugging, Test og Dokumentation

Kurt Nørmark
Institut for Datalogi, Aalborg Universitet


Sammendrag
Forrige lektion Næste lektion
Stikord Referencer Indhold
Når vi programmerer laver vi fejl. I denne lektion ser vi på hvordan vi håndterer sådanne fejl, og hvordan vi finder fejl via systematisk test.


Fejl

Forskellige slags fejl
Slide Indhold Stikord
Referencer 

  • Logiske fejl

    • Programmet implementerer en forkert algoritme

    • Løsningen er tænkt forkert - resultatet bliver forkert

  • Syntaksfejl

    • Programmet kan ikke oversættes

    • Programmeringssprogets regler er overtrådt

  • Køretidsfejl

    • Programmet kan oversættes

    • Programmet 'går ned' når vi kører det, eller programmet terminerer ikke (uendelig løkke)

Henvisning

Program: Programmet der beregner største fælles divisor med en funktion - OK.
/* OK */
#include <stdio.h>

int gcd(int, int);

int main(void) {
  int i, j, small = 42, large =98;
  printf("GCD of %d and %d is %d\n\n", small, large, gcd(large, small));
  return 0;
}

int gcd(int large, int small){
  int remainder; 
  while (small > 0){
    remainder = large % small;
    large = small;
    small = remainder;
  }
  return large;
}   

Program: Programmet med en logisk, algoritmisk fejl.
/* Logic error - wrong result */
#include <stdio.h>

int gcd(int, int);

int main(void) {
  int i, j, small = 42, large =98;
  printf("GCD of %d and %d is %d\n\n", small, large, gcd(large, small));
  return 0;
} 

int gcd(int large, int small){
  int remainder; 
  while (small > 0){
    remainder = small % large;
    large = small;
    small = remainder;
  }
  return large;
}   

Program: Programmet med en syntaksfejl.
/* Syntactical error */
#include <stdio.h>

int gcd(int, int);

int main(void) {
  int i, j, small = 42, large =98;
  printf("GCD of %d and %d is %d\n\n", small, large, gcd(large, small));
  return 0;
}

int gcd(int large, int small) do {
  int remainder; 
  while (small > 0){
    remainder := large % small;
    large = small;
    small = remainder;
  }
  return large;
}   

Program: Programmet med en køretidsfejl - måske afledt af en logisk fejl.
/* Run time error - may be caused by a logic error */
#include <stdio.h>

int gcd(int, int);

int main(void) {
  int i, j, small = 42, large =98;
  printf("GCD of %d and %d is %d\n\n", small, large, gcd(large, small));
  return 0;
}

int gcd(int large, int small){
  int remainder; 
  while (small >= 0){
    remainder = large % small;   /* Floating point exception (core dumped) */
    large = small;
    small = remainder;
  }
  return large;
}   

Program: Programmet med en uendelig løkke - måske afledt af en logisk fejl.
/* Infinite loop - may be caused by a logic error  */
#include <stdio.h>

int gcd(int, int);

int main(void) {
  int i, j, small = 42, large =98;
  printf("GCD of %d and %d is %d\n\n", small, large, gcd(large, small));
  return 0;
}

int gcd(int large, int small){
  int remainder; 
  while (small > 0){
    remainder = large % small;
    large = small;
    /* small never updated */
  }
  return large;
}   

Hvornår finder vi fejl?
Slide Indhold Stikord
Referencer 

Det er værdifuldt at finde fejl så tidligt som muligt i udviklingsprocessen

  • Tidspunkter hvor fejl bliver fundet:

    • Når vi arbejder med algoritmen - på design time

      • Godt!

    • Når programmet oversættes - på compile time

      • Attraktivt

      • Compiler med options der finder flest mulige fejl

    • Når programmet prøvekøres - på run time

      • Bedst hvis der er tale om systematisk test

      • Bedre sent end aldrig

    • Når kunden anvender programmet - også på run time

      • En tikkende bombe

      • En mulig 'katastrofe'

Hvordan finder vi fejl?
Slide Indhold Stikord
Referencer 

Hvordan finder vi ud af om der er fejl?

  • Fejl opdages tilfældigt, når vi sporadisk prøvekører programmet

    • Daglig praksis for mange af os...

    • Ikke godt nok!

  • Fejl opdages når vi tester programmet systematisk

    • Et stort, men nødvendigt arbejde

    • Meget mere om dette lidt senere i denne lektion

Fejl skal findes i en nøje tilrettelagt aktivitet under programudviklingen

Programtest

Hvordan finder vi årsagen til fejl?
Slide Indhold Stikord
Referencer 

Der er en logisk fejl eller en køretidsfejl i programmet.

Hvor er fejlen - hvad er årsagen til fejlen?

  • Hvordan finder vi årsagen til fejl?

    • Vi nærlæser programmet og ræsonnerer os til årsagen

    • Vi indsætter printf kald som sladrer om programtilstanden

    • Vi bruger en debugger

Hvordan håndterer vi fejl?
Slide Indhold Stikord
Referencer 

Hvordan reagerer programmet hvis programmet finder ud af, at der er sket en fejl?

  • Stop straks programmet

    • Som regel den bedste mulighed hvis der er fejl i programmet

    • Lad programmet rapportere detaljer om fejlen, så fejlen kan findes og rettes

    • Situation under programudvikling

  • Kør videre - råd bod på fejlen

    • I forbindelse med input fra brugeren

      • Kræver ofte yderligere dialog med brugeren

    • Fejl i andre situationer

      • Ofte meget vanskeligt 'at gøre noget ved det'

    • Situation under programdrift

Nyere programmeringssprog understøttet exception handling

Anvendes ofte til kontrolleret nedlukning af et program, snarere end egentlig programfortsættelse

Korrekthed - assertions
Slide Indhold Stikord
Referencer 

Det er muligt at 'forsyne' et program med logiske udtryk (assertions), som fortæller os om programmet opfører sig, som vi forventer

Facilitet som stilles til rådighed af assert.h

  • To specielt interessante assertions:

    • Præbetingelse af en funktion: Fortæller om det giver mening at kalde en funktion: Forudsætningen for kaldet.

    • Postbetingelse af en funktion: Fortæller om funktionen returnerer korrekte resultater.

Program: En forkert implementation af my_sqrt.
#include <stdio.h>
#include <math.h>
/* #define NDEBUG 1 */       /* if NDBUG is defined, the assert facility is disabled */
#include <assert.h>

int isSmallNumber(double x){
  return (fabs(x) < 0.0000001);
}   

double my_sqrt(double x){
  double res;
  assert(x >= 0);   /* PRECONDITION */
  res = x / 2;
  assert(isSmallNumber(res*res - x));   /* POSTCONDITION */
  return res;
}

int main(void) {

  printf("my_sqrt(15.0): %lf\n", my_sqrt(15.0));
  printf("my_sqrt(20.0): %lf\n", my_sqrt(20.0));
  printf("my_sqrt(2.0): %lf\n", my_sqrt(2.0));
  printf("my_sqrt(16.0): %lf\n", my_sqrt(16.0));
  printf("my_sqrt(-3.0): %lf\n", my_sqrt(-3.0));
  
  return 0;
}

Program: Program output.
a.exe: my-sqrt-incorrect.c:14: my_sqrt: Assertion `isSmallNumber(res*res - x)' failed.
Aborted

Program: En brugbar - men primitiv - implementation af my_sqrt implementeret via rodsøgningsfunktionen.
#include <stdio.h>
#include <math.h>
/* #define NDEBUG 1 */
#include <assert.h>

int isSmallNumber(double x){
  return (fabs(x) < 0.0000001);
}   

double displacement; 

double f (double x){
  return (x * x - displacement);
}

int sameSign(double x, double y){
  return x * y > 0.0;
}

double middleOf(double x, double y){
  return x + (y - x)/2;
}

double findRootBetween(double l, double u){
 while (!isSmallNumber(f(middleOf(l,u)))){ 
   if(sameSign(f(middleOf(l,u)), f(u)))
     u = middleOf(l,u);
   else 
     l = middleOf(l,u);
 }
 return middleOf(l,u);
}  


double my_sqrt(double x){
  double res;
  assert(x >= 0);
  displacement = x; 
  res = findRootBetween(0,x);
  assert(isSmallNumber(res*res - x));
  return res;
}

int main(void) {

  printf("my_sqrt(15.0): %lf\n", my_sqrt(15.0));
  printf("my_sqrt(20.0): %lf\n", my_sqrt(20.0));
  printf("my_sqrt(2.0): %lf\n", my_sqrt(2.0));
  printf("my_sqrt(16.0): %lf\n", my_sqrt(16.0));
  
  return 0;
}

Program: En udgave af findRootBetween med præbetingelse og postbetingelse.
double findRootBetween(double l, double u){
 double res;
 assert(l <= u); assert(!sameSign(f(l), f(u))); 
 while (!isSmallNumber(f(middleOf(l,u)))){ 
   if(sameSign(f(middleOf(l,u)), f(u)))
     u = middleOf(l,u);
   else 
     l = middleOf(l,u);
 }
 res = middleOf(l,u);
 assert(isSmallNumber(f(res)));
 return res;
}

Validering af brugerinput
Slide Indhold Stikord
Referencer 

Mange følgefejl kan forhindres hvis input fra brugeren altid valideres

I eksemplet skal vi have indlæst netop én double og én int

Program: Læsning af double og input med input validering.
#include <stdio.h>
#include <stdlib.h>

void clear_standard_input_line(void);
void get_double_int_input(void);

int main(void) {
  get_double_int_input();
  return 0;
}

void get_double_int_input(void) {
  double x = 0.0;
  int i = 0, input_result;

  // Prompting for input of a double and an int:
  printf("Enter a double and an integer:\n");
  input_result = scanf("%lf %d", &x, &i);

  // Handling of erroneous input - a non-controllable mess
  if (input_result == 0){
    // double and int reading both unsuccessful.
    clear_standard_input_line();
    
    // Prompt for both numbers again.
    printf("Serious now: Enter a double and an integer:\n");
    scanf("%lf %d", &x, &i);
  } 
  else if (input_result == 1){
    // int reading unsuccessful.
    clear_standard_input_line();

    // Prompt for the integer again:
    printf("Serious now: Enter the missing integer:\n");
    input_result = scanf("%d", &i);
  } 
  else if (input_result != 2){
     printf("Input of double and int. Should not happen. Bye");
     exit(EXIT_FAILURE);
  }
  else if (input_result == 2){
    clear_standard_input_line();  /* Clear rest of line */
  }  
  // We hope for the postcondition: input_result == 2


  // Proceed - input hopefully OK ?!
  printf("x = %f, i = %d\n", x, i);
}

void clear_standard_input_line(void){
  int ch;
  while ((ch = getchar()) != '\n' && ch != EOF);
}

Program: Læsning af double og input med input validering - i do-while input validerings løkke.
#include <stdio.h>
#include <stdlib.h>

void clear_standard_input_line(void);
void get_double_int_input(void);

int main(void) {
  get_double_int_input();
  return 0;
}

void get_double_int_input(void) {
  double x = 0.0;
  int i = 0, input_result;


  do {
    // Prompting for input of a double and an int:
    printf("Enter a double and an integer:\n");
    input_result = scanf("%lf %d", &x, &i);  

    if (input_result != 2){
       clear_standard_input_line();
       printf("Problems. Try again\n");
    }
  }  while (input_result != 2);

  // Proceed - input OK because input_result == 2;
  printf("x = %f, i = %d\n", x, i);
}

void clear_standard_input_line(void){
  int ch;
  while ((ch = getchar()) != '\n' && ch != EOF);
}

Program: Læsning af double og input med input validering - rekursivt funktionskald.
#include <stdio.h>
#include <stdlib.h>

void clear_standard_input_line(void);
void get_double_int_input(void);

int main(void) {
  get_double_int_input();
  return 0;
}

void get_double_int_input(void) {
  double x = 0.0;
  int i = 0, input_result;

  // Prompting for input of a double and an int:
  printf("Enter a double and an integer:\n");
  input_result = scanf("%lf %d", &x, &i);

  if (input_result != 2){
     clear_standard_input_line();
     printf("Problems. Try again\n");
     get_double_int_input();
  }
  else {
    // Proceed - input OK - input_result == 2;
    printf("x = %f, i = %d\n", x, i);
  }  
}

void clear_standard_input_line(void){
  int ch;
  while ((ch = getchar()) != '\n' && ch != EOF);
}

Opgave 7.2. Input validering med rekursiv funktion

Se på følgende variant af programmet vist på den tilknyttede slide. Hvad er problemet?

#include <stdio.h>
#include <stdlib.h>

void clear_standard_input_line(void);
void get_double_int_input(void);

int main(void) {
  get_double_int_input();
  return 0;
}

void get_double_int_input(void) {
  double x = 0.0;
  int i = 0, input_result;

  // Prompting for input of a double and an int:
  printf("Enter a double and an integer:\n");
  input_result = scanf("%lf %d", &x, &i);

  if (input_result != 2){
     clear_standard_input_line();
     printf("Problems. Try again\n");
     get_double_int_input();
  }

  // Proceed - input OK - input_result == 2;
  printf("x = %f, i = %d\n", x, i);
}

void clear_standard_input_line(void){
  int ch;
  while ((ch = getchar()) != '\n' && ch != EOF);
}

Funktioner der returnerer fejl-værdier
Slide Indhold Stikord
Referencer 

Mange standard C funktioner returnerer en værdi, som indikerer om der er sket en fejl

  • Funktioner der returnerer værdi, som kan indikere fejl

    • main: Returnerer EXIT_FAILURE hvis der er problemer

    • scanf: Returnerer antallet af matches (antallet af assignments) eller EOF

    • fopen: Returnerer en null pointer hvis filen ikke kan åbnes

    • getchar: Returnerer EOF (-1) hvis der mødes et EOF. Returværditypen er int, ikke char

Funktioners returværdi kan - generelt set - ikke bruges til både funktionens resultat og til en fejl kode

Fejlnumre og fejlmeddelelser i errno.h
Slide Indhold Stikord
Referencer 

Der findes en facilitet i C, errno.h, som understøtter rapportering af fejl-koder fra nogle funktioner

Program: Et eksempel på brug af errno fra errno.h.
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <errno.h>

int main(void) {
  double input = -1.0, x = 0.0;

  /* Take square root of a negative number, prepare for error reporting */ 
  errno = 0;         /* Reset errno */

  x = sqrt(input);   /* sqrt assigns errno in case of error */

  if (errno){  /* errno is positive in case of an error */
     printf("sqrt failed, code: %d, message: '%s'\n", errno, strerror(errno));
     x = 0.0;
  }
  printf("Result is %f\n", x);

  return 0;
}

Program: Output fra programmet.
sqrt failed, code: 33, message: 'Domain error'
Result is 0.000000

Program: Et mislykket eksempel på brug af errno - i forbindelse med division.
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>

int main(void) {
  int x = 0, input = 0;

  errno = 0;

  /* Divide by zero */
  x = 1 / input;        /* errno is not changed by the division operator   */
                      
  if (errno){
     printf("Division failed, code: %d, message: '%s'\n", errno, strerror(errno));
     x = INT_MAX;
  }

  printf("Result is %d\n", x);

  return 0;
}

Program: Godt alternativ: Forhindring af fejl.
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

int main(void) {
  int x = 0, input = 0;

  /* Prevent division by zero */
  if (input != 0)
      x = 1 / input;        /* errno is not changed by the division operator   */
  else{
    printf("Division by zero avoided\n");
    x = INT_MAX;
  }

  printf("Result is %d\n", x);

  return 0;
}

Program: Output fra programmet.
Division by zero avoided
Result is 2147483647


Test af Funktioner

Sporadisk afprøvning
Slide Indhold Stikord
Referencer 

Sporadisk program afprøvning er en forholdvis tilfældig prøvekørsel af et program

  • Sporadisk afprøvning:

    • f afprøves på et eller andet tidspunkt når programmet omkring den kan udføres

      • Typiske gennem et direkte eller indirekte kald af f fra main

    • Valg af input er 'det første og bedste' eller ikke under kontrol

    • Vurdering af output er sporadisk

      • Kaldet af f er OK hvis det omkringliggende program 'ikke går ned'.

    • Det er svært at gentage afprøvningen på et senere tidspunkt

Program: Eksempel på sporadisk afprøvning af daysInMonth og isLeapYear.
#include <stdio.h>
#include <stdlib.h>

int daysInMonth(int mth, int yr);
int isLeapYear(int yr);

int main(void) {

  int mth, yr;

  do{
    printf("Enter a month - a number between 1 and 12: ");
    scanf("%d", &mth);
    printf("Enter a year: ");
    scanf("%d", &yr);

    if (yr != 0)
       printf("There are %d days in month %d in year %d\n",
               daysInMonth(mth, yr), mth, yr);
  } while (yr != 0);

  return 0;
}

int daysInMonth(int month, int year){
  int numberOfDays;
  switch(month){
    case 1: case 3: case 5: case 7: case 8: case 10: case 12: 
      numberOfDays = 31; break;
    case 4: case 6: case 9: case 11: 
      numberOfDays = 30; break;
    case 2:
      if (isLeapYear(year)) numberOfDays = 29; 
      else numberOfDays = 28; break;
    default: exit(-1);  break;
  }
  return numberOfDays;
}   

int isLeapYear(int year){
  int res;
  if (year % 400 == 0)
    res = 1;
  else if (year % 100 == 0)
    res = 0;
  else if (year % 4 == 0)
    res = 1;
  else res = 0;
  return res;
}  

Systematisk test
Slide Indhold Stikord
Referencer 

Systematisk test er en væsentlig opstramning i forhold til sporadisk afprøvning

Unit testing - enheden for test er en funktion

  • Systematisk test:

    • Funktionen testes umiddelbart efter at den er programmeret

      • Testen forberedes evt. inden f programmeres

    • Et antal kald af f med nøje udvalgt input

    • Omhyggelig vurdering af om output af f er korrekt

    • Testen kan forholdsvis let gentages når programmet/funktionen ændres

Det er bedre at teste én funktion (med input og output) end et helt program, med mange funktioner

Black box test
Slide Indhold Stikord
Referencer 

En black box test skal afsløre om en funktion giver korrekte resultater (output) på udvalgte input

Black box test tager udgangspunkt i de funktionelle krav til funktionen - ikke funktionens implementationsdetaljer

  • Udfordringer i black box testing:

    • Vi kan ikke teste en funktion på alle mulige input

    • Derfor skal vi udvælge gode repræsentanter for input til funktionen

    • Endvidere skal vi ræsonnere os frem til det forventede output fra det valgte input

    • Det beregnede output skal sammenlignes med det forventede output

Program: Diskussion af black box test af funktionen isLeapYear - ud fra funktionelle krav.
/* Returns if year is a leap year 
   Input: A legal year - a non-negative integer.
   Output: Whether year is a leap year. An integer interpreted as a boolean value.
*/

int isLeapYear(int year);

Program: Selve funktionen isLeapYear - fokus på de funktionelle krav snarere end funktionens implementation.
/* Returns if year is a leap year 
   Input: A legal year - a non-negative integer.
   Output: Whether year is a leap year. An integer interpreted as a boolean value.
*/

int isLeapYear(int year){
  int res;
  if (year % 400 == 0)
    res = 1;
  else if (year % 100 == 0)
    res = 0;
  else if (year % 4 == 0)
    res = 1;
  else res = 0;
  return res;
}  

Program: Diskussion af black box test af funktionen daysInMonth - ud fra funktionelle krav.
/* Returns the number of days in month, in the given year.
   Input: month is an integer between 1 and 12.
          year is a legal year (a non-negative integer).
   Output: The number of days in month in the given year. An integer.
*/

int daysInMonth(int month, int year);

Program: Selve funktionen daysInMonth - fokus på de funktionelle krav snarere end funktionens implementation.
/* Returns the number of days in month, in the given year.
   Input: month is an integer between 1 and 12.
          year is a legal year (a non-negative integer).
   Output: The number of dayns in month in the given year. An integer.
*/

int daysInMonth(int month, int year){
  int numberOfDays;
  switch(month){
    case 1: case 3: case 5: case 7: case 8: case 10: case 12: 
      numberOfDays = 31; break;
    case 4: case 6: case 9: case 11: 
      numberOfDays = 30; break;
    case 2:
      if (isLeapYear(year)) numberOfDays = 29; 
      else numberOfDays = 28; break;
    default: exit(-1);  break;
  }
  return numberOfDays;
}   

Opgave 7.3. Test af programmet der beregner timer, minutter og sekunder

Vi har i rigt mål beskæftiget os med et program, der beregner timer, minutter og sekunder af et antal sekunder. Senest har vi i Opgave 6.2 programmeret en funktion hours_minutes_seconds, som er et godt udgangspunkt for denne opgave.

Lav nu en systematisk test (black box unit testing) af følgende ønskede input og outputs til funktionen hours_minutes_seconds:

  • Input: 4000 sekunder. Forventet output: 1 timer, 6 minutter og 40 sekunder
  • Input: 75 sekunder. Forventet output: 0 timer, 1 minutter og 15 sekunder
  • Input: 3700 sekunder. Forventet output: 1 timer, 1 minutter og 40 sekunder
  • Input: 55 sekunder. Forventet output: 0 timer, 0 minutter og 55 sekunder
  • Input: 3661 sekunder. Forventet output: 1 timer, 1 minutter og 1 sekunder

Du kan enten programmere dine tests med assert, eller med brug af CUTest. Hvis du bruger CuTest er detaljerne fra denne slide et godt udgangspunkt (kopier gerne fra slides programmerne).

Bemærk lige at vi i denne opgave ikke tester output formatet (som gjort i opgave 3.4) - kun beregningerne af normaliserede timer, minutter og sekunder.

White box test
Slide Indhold Stikord
Referencer 

Konstrasten til black box test er white box testing, som tager udgangspunkt i kontrolstrukturerne i funktionen

White box testing sigter bl.a. efter at alle dele af funktionen er udført mindst én gang

Top-down test af funktioner
Slide Indhold Stikord
Referencer 

Top-down programmering indebærer helt naturligt top-down test

  • Top-down test

    • Funktionen under test - fut

    • Stubbe af endnu ikke programmerede funktioner som kaldes af fut

    • Driver, som kalder fut med kontrolleret input, og som formidler resultatet af kaldet

  • Kontrast: Bottom-up test

    • Naturlig test teknik ved bottom-up programmering - typisk OOP

    • Der er sjældent behov for stubbe, idet funktionerne tilvejebringes i bottom-up orden

Henvisning

Figur. Illustration of function under test i relation til stubbe og driver.

Systematisk test af C programmer
Slide Indhold Stikord
Referencer 

Vi viser hvordan vi manuelt kan gennemføre en systematisk top-down test af et C program

Program: Funktionen daysInMonth og tilhørende tests - isLeapYear er en stub.
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int isLeapYear(int yr);

/* Function Under Test */
int daysInMonth(int month, int year){
  int numberOfDays;
  switch(month){
    case 1: case 3: case 5: case 7: case 8: case 10: case 12: 
      numberOfDays = 31; break;
    case 4: case 6: case 9: case 11: 
      numberOfDays = 30; break;
    case 2:
      if (isLeapYear(year)) numberOfDays = 29; 
      else numberOfDays = 28; break;
    default: exit(-1);  break;
  }
  return numberOfDays;
}   


/* Test functions of daysInMonth: */

void testDaysInMonthJan(void){
  int actual = daysInMonth(1, 2010);
  int expected = 31;
  assert(actual == expected);
}

void testDaysInMonthFeb(void){
  int actual = daysInMonth(2, 2010);
  int expected = 28;
  assert(actual == expected);
}

void testDaysInMonthApr(void){
  int actual = daysInMonth(4, 2010);
  int expected = 30;
  assert(actual == expected);
}

void testDaysInMonthDec(void){
  int actual = daysInMonth(12, 2010);
  int expected = 31;
  assert(actual == expected);
}  


/* Stub */
int isLeapYear(int year){
  return 0;
}

Program: Test driver for tests af funktionen daysInMonth.
#include <stdio.h>
    
void RunAllTests(void) {
  testDaysInMonthJan();
  testDaysInMonthFeb();
  testDaysInMonthApr();
  testDaysInMonthDec();
}
    
int main(void) {
    RunAllTests();
}

Program: Oversættelse og kørsel af programmet.
> gcc -c days-month-leap.c
> gcc days-month-leap.o all-tests.c -o all-tests
> ./all-tests.exe
>

Program: Oversættelse og kørsel af programmet - med forventing om 30 dage i januar.
/* Tested with 30 expected days in January */

> gcc -c days-month-leap.c
> gcc days-month-leap.o all-tests.c -o all-tests
> ./all-tests.exe
assertion "actual == expected" failed: file "days-month-leap.c", line 29, function: testDaysInMonthJan
Aborted (core dumped)

Program: Funktionen daysInMonth, isLeapYear og alle tilhørende tests.
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int isLeapYear(int yr);

/* Function Under Test */
int daysInMonth(int month, int year){
  int numberOfDays;
  switch(month){
    case 1: case 3: case 5: case 7: case 8: case 10: case 12: 
      numberOfDays = 31; break;
    case 4: case 6: case 9: case 11: 
      numberOfDays = 30; break;
    case 2:
      if (isLeapYear(year)) numberOfDays = 29; 
      else numberOfDays = 28; break;
    default: exit(-1);  break;
  }
  return numberOfDays;
}   


/* Test functions of daysInMonth: */

void testDaysInMonthJan(void){
  int actual = daysInMonth(1, 2010);
  int expected = 31;
  assert(actual == expected);
}

void testDaysInMonthFeb(void){
  int actual = daysInMonth(2, 2010);
  int expected = 28;
  assert(actual == expected);
}

void testDaysInMonthApr(void){
  int actual = daysInMonth(4, 2010);
  int expected = 30;
  assert(actual == expected);
}

void testDaysInMonthDec(void){
  int actual = daysInMonth(12, 2010);
  int expected = 31;
  assert(actual == expected);
}  


int isLeapYear(int year){
  int res;
  if (year % 400 == 0)
    res = 1;
  else if (year % 100 == 0)
    res = 0;
  else if (year % 4 == 0)
    res = 1;
  else res = 0;
  return res;
}

/* A number of test functions of isLeapYear */

void testIsLeapYear1999(void){
  int actual = isLeapYear(1999);
  int expected = 0;
  assert(actual == expected);
}

void testIsLeapYear2000(void){
  int actual = isLeapYear(2000);
  int expected = 1;
  assert(actual == expected);
}

void testIsLeapYear1900(void){
  int actual = isLeapYear(1900);
  int expected = 0;
  assert(actual == expected);
}

void testIsLeapYear2008(void){
  int actual = isLeapYear(2008);
  int expected = 1;
  assert(actual == expected);
}  

Program: Test driver for tests af funktionenerne daysInMonth og isLeapYear.
#include <stdio.h>
    
void RunAllTests(void) {
  testDaysInMonthJan();
  testDaysInMonthFeb();
  testDaysInMonthApr();
  testDaysInMonthDec();

  testIsLeapYear1999();
  testIsLeapYear2000();
  testIsLeapYear1900();
  testIsLeapYear2008();
}
    
int main(void) {
    RunAllTests();
}

Program: Oversættelse og kørsel af programmet.
> gcc -c days-month-leap.c
> gcc days-month-leap.o all-tests.c -o all-tests
> ./all-tests.exe
>

Alt er OK når der ikke kommer noget output fra ovenstående test driver programmer

Opgave 7.4. Test af rod beregning i andengradspolynomium

Test funktionen solveQuadraticEquation fra en tidligere lektion, som er den funktion der sender rødderne tilbage gennem output parametre.

Tag gerne udgangspunkt i løsningen på opgave 5.1, og lav top-down test af de fire funktioner solveQuadraticEquation, discriminant, root1 og root2. Med andre ord skal du altså teste den variant af programet som har fire funktioner (solveQuadraticEquation, discriminant-funktionen, og de to rod-funktioner), og hvor solveQuadraticEquation sender resultaterne tilbage gennem fire pointere.

Overvej behovet for stubbe når du laver testcases for disse funktioner.

Du skal gennemføre en systematisk (black box unit) test, som udvælger et mindre antal testcases, der har størst mulighed for at finde fejl. Programmer hver test case som en lille funktion. Du kan enten gennemføre testarbejdet med asssert eller med brug af CUTest.

Følg mønstret for tests af daysInMonth og isLeapYear på den tilhørende slide.

Systematisk test af C programmer med CUTest
Slide Indhold Stikord
Referencer 

Stubbe, drivere, samt vurderinger af output i forhold til input programmeres alt sammen med håndkraft

Udvalgte dele automatiseres gennem anvendelse af et unit testing framework

Vi illustrerer her CUTest - et meget simpelt unit testing framework for C

Program: Funktionen daysInMonth og tilhørende tests.
#include <stdio.h>
#include <stdlib.h>
#include "CuTest.h"

int isLeapYear(int yr);

/* Function Under Test */
int daysInMonth(int month, int year){
  int numberOfDays;
  switch(month){
    case 1: case 3: case 5: case 7: case 8: case 10: case 12: 
      numberOfDays = 31; break;
    case 4: case 6: case 9: case 11: 
      numberOfDays = 30; break;
    case 2:
      if (isLeapYear(year)) numberOfDays = 29; 
      else numberOfDays = 28; break;
    default: exit(-1);  break;
  }
  return numberOfDays;
}   

/* Test function of daysInMonth */
void testDaysInMonthJan(CuTest *tc){
  int actual = daysInMonth(1, 2010);
  int expected = 31;
  CuAssertIntEquals(tc, expected, actual);
}

void testDaysInMonthFeb(CuTest *tc){
  int actual = daysInMonth(2, 2010);
  int expected = 28;
  CuAssertIntEquals(tc, expected, actual);
}

void testDaysInMonthApr(CuTest *tc){
  int actual = daysInMonth(4, 2010);
  int expected = 30;
  CuAssertIntEquals(tc, expected, actual);
}

void testDaysInMonthDec(CuTest *tc){
  int actual = daysInMonth(12, 2010);
  int expected = 31;
  CuAssertIntEquals(tc, expected, actual);
}  

/* Stub */
int isLeapYear(int year){
  return 0;
}

/* Test case management: Adding the test case to a test suite */
CuSuite* daysInMonthGetSuite(void) {
  CuSuite* suite = CuSuiteNew();
  SUITE_ADD_TEST(suite, testDaysInMonthJan);
  SUITE_ADD_TEST(suite, testDaysInMonthFeb);
  SUITE_ADD_TEST(suite, testDaysInMonthApr);
  SUITE_ADD_TEST(suite, testDaysInMonthDec);
  return suite;
}

Program: Testdriver for tests af funktionen daysInMonth.
#include "CuTest.h"
#include <stdio.h>

CuSuite* daysInMonthGetSuite(void);    
    
void RunAllTests(void) {
  CuString *output = CuStringNew();
  CuSuite* suite = CuSuiteNew();
     
  CuSuiteAddSuite(suite, daysInMonthGetSuite());  /* Adding our test suite */
 
  CuSuiteRun(suite);
  CuSuiteSummary(suite, output);
  CuSuiteDetails(suite, output);
  printf("%s\n", output->buffer);
}
    
int main(void) {
    RunAllTests();
}

Program: Kompilering og kørsel.
> gcc all-tests.c CuTest.c days-month-leap.c

> ./a.out
....
OK (4 tests)

Program: Kompilering og kørsel - med forventing om 30 dage i januar.
/* Tested with 30 expected days in January */

There was 1 failure:
1) testDaysInMonthJan: days-month-leap.c:27: expected <30> but was <31>

!!!FAILURES!!!
Runs: 4 Passes: 3 Fails: 1

Program: Funktionen isLeapYear og tilhørende tests - tilføjet til tests af daysInMonth.
#include <stdio.h>
#include <stdlib.h>
#include "CuTest.h"

int isLeapYear(int yr);

int daysInMonth(int month, int year){
  int numberOfDays;
  switch(month){
    case 1: case 3: case 5: case 7: case 8: case 10: case 12: 
      numberOfDays = 31; break;
    case 4: case 6: case 9: case 11: 
      numberOfDays = 30; break;
    case 2:
      if (isLeapYear(year)) numberOfDays = 29; 
      else numberOfDays = 28; break;
    default: exit(-1);  break;
  }
  return numberOfDays;
}   

/* A number of test functions of daysInMonth */

void testDaysInMonthJan(CuTest *tc){
  int actual = daysInMonth(1, 2010);
  int expected = 31;
  CuAssertIntEquals(tc, expected, actual);
}

void testDaysInMonthFeb(CuTest *tc){
  int actual = daysInMonth(2, 2010);
  int expected = 28;
  CuAssertIntEquals(tc, expected, actual);
}

void testDaysInMonthApr(CuTest *tc){
  int actual = daysInMonth(4, 2010);
  int expected = 30;
  CuAssertIntEquals(tc, expected, actual);
}

void testDaysInMonthDec(CuTest *tc){
  int actual = daysInMonth(12, 2010);
  int expected = 31;
  CuAssertIntEquals(tc, expected, actual);
}

/* Test case management: Adding test cases to a test suite */
CuSuite* daysInMonthGetSuite() {
  CuSuite* suite = CuSuiteNew();
  SUITE_ADD_TEST(suite, testDaysInMonthJan);
  SUITE_ADD_TEST(suite, testDaysInMonthFeb);
  SUITE_ADD_TEST(suite, testDaysInMonthApr);
  SUITE_ADD_TEST(suite, testDaysInMonthDec);

  return suite;
}


/* Function Under Test */
int isLeapYear(int year){
  int res;
  if (year % 400 == 0)
    res = 1;
  else if (year % 100 == 0)
    res = 0;
  else if (year % 4 == 0)
    res = 1;
  else res = 0;
  return res;
}  

/* A number of test functions of isLeapYear */

void testIsLeapYear1(CuTest *tc){
  int actual = isLeapYear(1999);
  int expected = 0;
  CuAssertIntEquals(tc, expected, actual);
}

void testIsLeapYear2(CuTest *tc){
  int actual = isLeapYear(2000);
  int expected = 1;
  CuAssertIntEquals(tc, expected, actual);
}

void testIsLeapYear3(CuTest *tc){
  int actual = isLeapYear(1900);
  int expected = 0;
  CuAssertIntEquals(tc, expected, actual);
}

void testIsLeapYear4(CuTest *tc){
  int actual = isLeapYear(2008);
  int expected = 1;
  CuAssertIntEquals(tc, expected, actual);
}  


/* Test case management: Adding test cases to a test suite */
CuSuite* isLeapYearGetSuite() {
  CuSuite* suite = CuSuiteNew();

  SUITE_ADD_TEST(suite, testIsLeapYear1);
  SUITE_ADD_TEST(suite, testIsLeapYear2);
  SUITE_ADD_TEST(suite, testIsLeapYear3);
  SUITE_ADD_TEST(suite, testIsLeapYear4);

  return suite;
}

Program: Testdriver for tests af daysInMonth og isLeapYar.
#include "CuTest.h"
#include <stdio.h>
    
CuSuite* StrUtilGetSuite();
    
void RunAllTests(void) {
  CuString *output = CuStringNew();
  CuSuite* suite = CuSuiteNew();
     
  // Adding test suites:
  CuSuiteAddSuite(suite, (CuSuite*)daysInMonthGetSuite());
  CuSuiteAddSuite(suite, (CuSuite*)isLeapYearGetSuite());  
 
  CuSuiteRun(suite);
  CuSuiteSummary(suite, output);
  CuSuiteDetails(suite, output);
  printf("%s\n", output->buffer);
}
    
int main(void) {
    RunAllTests();
}

Program: Kompilering og kørsel.
> gcc all-tests.c CuTest.c days-month-leap.c

> a.exe
........

OK (8 tests)

Program: Testdriver (uændret) og en alsidig variant af main som både rummer normal kørsel og testkørsel.
#include <stdio.h>
#include "CuTest.h"

int daysInMonth(int month, int yr);
int isLeapYear(int yr);    

void RunAllTests(void) {
  CuString *output = CuStringNew();
  CuSuite* suite = CuSuiteNew();
     
  // Adding test suites:
  CuSuiteAddSuite(suite, (CuSuite*)daysInMonthGetSuite());
  CuSuiteAddSuite(suite, (CuSuite*)isLeapYearGetSuite());  
 
  CuSuiteRun(suite);
  CuSuiteSummary(suite, output);
  CuSuiteDetails(suite, output);
  printf("%s\n", output->buffer);
}

int main(int argc, char *argv[]) {

  int mth, yr, i;

  if (argc == 2 && strcmp(argv[1], "--test") == 0)  
    RunAllTests();
  else {
    do{
      printf("Enter a month - a number between 1 and 12: ");
      scanf("%d", &mth);
      printf("Enter a year: ");
      scanf("%d", &yr);
  
      printf("There are %d days in month %d in year %d\n",
                daysInMonth(mth, yr), mth, yr);
    } while (yr != 0);
  }

  return 0;
}

Program: Kompilering og kørsel - normal og test.
> gcc all-tests.c CuTest.c days-month-leap.c

> a.exe
Enter a month - a number between 1 and 12: 11
Enter a year: 2010
There are 30 days in month 11 in year 2010
Enter a month - a number between 1 and 12: 12
Enter a year: 0
There are 31 days in month 12 in year 0


> a.exe --test
........

OK (8 tests)

Opgave 7.5. Kom godt i gang med CUTest

Formålet med denne opgave at hjælpe dig i gang med brug af unit testing frameworket CUTest for C programmer. Der findes også en video som hjælper dig i gang med CuTest.

Naviger til CUTest siden og download CUTest. I 2020 har den seneste version af CUTest nummer 1.5. Det du downloader er en ganske lille pakket fil (en zip-fil) med nogle få C programmer. Udtræk filerne fra den pakkede fil, og læs derefter README file. Denne udgør brugervejledningen til CUTest.

Når du skal bruge CUTest er det lettest at kopiere filerne CUTest.C og CUTest.h til det katalog, hvor du har det C program, som du ønsker at teste.

I README filen kan du læse, hvordan du oversætter og kører et testprogram, som er lavet med brug af CUTest. Prøv det gerne selv af på de programmer med tests, der er vist på den tilhørende slide (eller på et af dine egne programmer som du ønsker at teste).

Refleksioner omkring CUTest
Slide Indhold Stikord
Referencer 

CUTest er det Unit Test Framework der blev anvendt på forrige slide

  • Hvad tilbyder CUTest?

    • Assertions der sammenligner aktuelt og forventet resultat

    • Organisering af tests i en hierarkisk struktur

    • Status over kørsel af alle tests

  • Hvad tilbyder CUTest ikke?

    • Automatisk identifikation af test funktioner

    • Automatisk kørsel af test suiter

  • Observationer:

    • Testfunktioner og funktioner under test lever side om side i samme fil

    • Man kan let organisere at et program enten opfører sig normalt, eller at det kører alle tests

Unanset hvad - Det er et STORT ARBEJDE at gennemføre unit testing af et program


Dokumentation af Funktioner

Dokumentation af Funktioner
Slide Indhold Stikord
Referencer 

Programdokumentation er naturlig engelsk eller dansk tekst, som fastholder forståelsen af et stykke program

  • Programdokumentation i et C program

    • Skrives som kommentarer:

      • Delimited comments: /* ... */      - Kan ikke indlejres

      • Single line comments: // ...      - Ikke ANSI C

    • Kan knyttes til program-dele på forskellige niveauer:

      • Udvalgte detaljer - enkelte kommandoer eller erklæringer

      • Sektioner af kommandoer og erklæringer

      • Funktioner - forklaring af en funktion, herunder funktionens input og output

      • Hele programmet - beskrivelse og forklaring af programmet som helhed

    • Kan ved brug af et dokumentationsprogram trækkes ud af C kildeprogrammet, og præsenteres som et separat dokument

      • Doxygen kan anbefales

Eksempler på dokumentation af funktioner og Doxygen
Slide Indhold Stikord
Referencer 

Vi viser eksempler på underdokumenterede, overdokumenterede og sektions-dokumenterede programmer.

Endvidere viser vi hvad Doxygen tilbyder.

Program: Hele rodsøgningsprogrammet - underdokumenteret.
#include <stdio.h>
#include <math.h>

int sameSign(double x, double y);
double middleOf(double x, double y);
int isSmallNumber(double x);
double findRootBetween(double a, double b);
 
double f(double x){
  /* (x - 5.0) * (x - 3.0) * (x + 7.0) */
  return (x*x*x - x*x - 41.0 * x + 105.0);
}

int main(void){
  double x, y;
  int numbers; 

  do{
    printf("%s","Find a ROOT between two number: ");
    numbers = scanf("%lf%lf", &x, &y);

    if (numbers == 2 && !sameSign(f(x),f(y))){
      double solution = findRootBetween(x,y);
      printf("\nThere is a root in %lf\n", solution);
    }
    else if (numbers == 2 && sameSign(f(x),f(y)))
      printf("\nf must have different signs in %lf and %lf\n",
                x, y);
    else if (numbers != 2)
      printf("\nBye\n\n");
  }
  while (numbers == 2);

  return 0;
}

/* Precondition: The signs of f(a) and f(b) are different */
double findRootBetween(double a, double b){
  double l = a, u = b;
  while (!isSmallNumber(f(middleOf(l,u)))){ 
    if(sameSign(f(middleOf(l,u)), f(u)))
      u = middleOf(l,u);
    else 
      l = middleOf(l,u);
  }
  return middleOf(l,u);
}  

int sameSign(double x, double y){
  return (x > 0 && y > 0) || (x < 0 && y < 0);
}

double middleOf(double x, double y){
  return x + (y - x)/2;
}

int isSmallNumber(double x){
  return (fabs(x) < 0.0000001);
}   

Program: Dokumentation af program og funktioner i rødsøgingsprogrammet.
/*  A program that finds a root in a continuous function f by use of the
 *  bisection method.
 *  Programmer: Kurt Normark, Aalborg University, normark@cs.aau.dk.
 *  Version 0.9, September 15, 2010.
 */

#include <stdio.h>
#include <math.h>

/*  The function in which we search for a root */ 
double f (double x){
  /* (x - 5.0) * (x - 3.0) * (x + 7.0) */
  return (x*x*x - x*x - 41.0 * x + 105.0);
}

/*  Return whether x and y have the same sign */
int sameSign(double x, double y){
  return (x > 0 && y > 0) || (x < 0 && y < 0);
}

/*  Return the mid point in between x and y */ 
double middleOf(double x, double y){
  return x + (y - x)/2;
}

/*  Is x considered to be very close to 0.0 */
int isSmallNumber(double x){
  return (fabs(x) < 0.0000001);
}   

/*  Search for a root of the continuous function f between the parameters 
    l and u. A root is a double r for which f(r) is very close to 0.0.  As a
    precondition it is assumed that the sign of f(l) and f(u) are different. */
double findRootBetween(double l, double u){
 while (!isSmallNumber(f(middleOf(l,u)))){ 
   if(sameSign(f(middleOf(l,u)), f(u)))
     u = middleOf(l,u);
   else 
     l = middleOf(l,u);
 }
 return middleOf(l,u);
}  

/*  A sample interactive driver of the root searching for f. */ 
int main (void){
    double x, y;
    int numbers; 

    do{
      printf("%s","Find a ROOT between which numbers: ");
      numbers = scanf("%lf%lf", &x, &y);

      if (numbers == 2 && !sameSign(f(x),f(y))){
          double solution = findRootBetween(x,y);
          printf("\nThere is a root in %lf\n", solution);
        }
      else if (numbers == 2 && sameSign(f(x),f(y)))
          printf("\nf must have different signs in %lf and %lf\n",
                  x, y);
      else if (numbers != 2)
          printf("\nBye\n\n");
    }
    while (numbers == 2);

    return 0;
}

Program: Hele rodsøgningsprogrammet - overdokumenteret.
/*  A program that finds a root in a continuous function f by use of the
 *  bisection method.
 *  Programmer: Kurt Normark, Aalborg University, normark@cs.aau.dk.
 *  Version 0.9, September 15, 2010.
 */

#include <stdio.h>     // Include the standard output library
#include <math.h>      // Include the math library

/*  The function in which we search for a root */ 
double f (double x){
  /* (x - 5.0) * (x - 3.0) * (x + 7.0) */
  /* Return the value of x cubic minus x squre minus 41 times x plus 105 */
  return (x*x*x - x*x - 41.0 * x + 105.0); 
}

/*  Return whether x and y have the same sign */
int sameSign(double x, double y){
  return x * y >= 0.0;   // Return the value of x times y compared with 0.0
}

/*  Return the mid point in between x and y */ 
double middleOf(double x, double y){
  return x + (y - x)/2;  // Return the value of x plus (y - y)/2
}

/*  Is x considered to be very close to 0.0 */
int isSmallNumber(double x){
  return (fabs(x) < 0.0000001);             // Evaluate if x is smaller than 0.0000001
}   

/*  Search for a root of the continuous function f between the parameters l and u.
    A root is a double r for which f(r) is very close to 0.0.
    As a precondition it is assumed that the sign of f(l) and f(u) are different. */
double findRootBetween(double l, double u){
 while (!isSmallNumber(f(middleOf(l,u)))){  // if f applied on the middle of 
                                            // l and y is not a small number

   if(sameSign(f(middleOf(l,u)), f(u)))     // if f(middle) and f(u) have same sign
     u = middleOf(l,u);                     // u becomes the middle of l and u.
                                            // l is not changed.
   else 
     l = middleOf(l,u);                     // l becomes the middle of l and u.
                                            // u is not changed.
 }
 return middleOf(l,u);                      // the result is the middle of l and u.
}  

/*  A sample interactive driver of the root searching for f. */ 
int main (void){
  double x, y;        // x and y are doubles.
  int numbers;        // numbers is an int.

  do{
      printf("%s","Find a ROOT between which numbers: ");  // Prompt for an interval
      numbers = scanf("%lf%lf", &x, &y);                   // Input x and y.

      if (numbers == 2 && !sameSign(f(x),f(y))){           // Do we get both x and y, and
                                                           // are f(x) and f(y) are not of the same sign
          double solution = findRootBetween(x,y);          // Find the root of f between x and y
          printf("\nThere is a root in %lf\n", solution);  // Print the root
        }
      else if (numbers == 2 && sameSign(f(x),f(y)))        // We got x and y, but
                                                           // f(x) and f(y) are of the same sign
          printf("\nf must have different signs in %lf and %lf\n",
                 x, y);                                    // Print error message
      else if (numbers != 2)                               // We did not read x and y. Exit!
          printf("\nBye\n\n");
    }
  while (numbers == 2);                                    // Run the program as long 
                                                           // as we read x and y
  return 0;
}

Program: Hele rodsøgningsprogrammet - main er sektions-dokumenteret.
/*  A program that finds a root in a continuous function f by use of the
 *  bisection method.
 *  Programmer: Kurt Normark, Aalborg University, normark@cs.aau.dk.
 *  Version 0.9, September 15, 2010.
 */

#include <stdio.h>
#include <math.h>

/*  The function in which we search for a root */ 
double f (double x){
  /* (x - 5.0) * (x - 3.0) * (x + 7.0) */
  return (x*x*x - x*x - 41.0 * x + 105.0);
}

/*  Return whether x and y have the same sign */
int sameSign(double x, double y){
  return (x > 0 && y > 0) || (x < 0 && y < 0);
}

/*  Return the mid point in between x and y */ 
double middleOf(double x, double y){
  return x + (y - x)/2;
}

/*  Is x considered to be very close to 0.0 */
int isSmallNumber(double x){
  return (fabs(x) < 0.0000001);
}   

/*  Search for a root of the continuous function f between the parameters l and u.
    A root is a double r for which f(r) is very close to 0.0.
    As a precondition it is assumed that the sign of f(l) and f(u) are different. */
double findRootBetween(double l, double u){
 while (!isSmallNumber(f(middleOf(l,u)))){ 
   if(sameSign(f(middleOf(l,u)), f(u)))
     u = middleOf(l,u);
   else 
     l = middleOf(l,u);
 }
 return middleOf(l,u);
}  

/*  A sample interactive driver of the root searching for f. */ 
int main (void){
    double x, y;
    int numbers; 

    // Repeat until scanf does not read two doubles:
    do{

      // Prompt user for input:
      printf("%s","Find a ROOT between which numbers: ");
      numbers = scanf("%lf%lf", &x, &y);

      // Distinguish between 3 cases:
      if (numbers == 2 && !sameSign(f(x),f(y))){      // The expected case
          double solution = findRootBetween(x,y);
          printf("\nThere is a root in %lf\n", solution);
        }

      else if (numbers == 2 && sameSign(f(x),f(y)))   // Same sign case
          printf("\nf must have different signs in %lf and %lf\n",
                  x, y);

      else if (numbers != 2)                          // Exit case
          printf("\nBye\n\n");
    }
    while (numbers == 2);

    return 0;
}

Program: Dokumentation af funktionerne i rodsøgningsprogrammet - beregnet for Doxygen.
/* A program that finds a root in a continuous function f by use of the
 * bisection method.
 * Programmer: Kurt Normark, Aalborg University, normark@cs.aau.dk.
 * Version 0.9, September 15, 2010.
 */

#include <stdio.h>
#include <math.h>

/** The function in which we search for a root. 
    @param[in] x The input to the function f. */ 
double f (double x){
  /* (x - 5.0) * (x - 3.0) * (x + 7.0) */
  return (x*x*x - x*x - 41.0 * x + 105.0);
}

/** Return whether x and y have the same sign.
    @param[in] x The first double the sign of which is to be compared with y.
    @param[in] y The second double the sign of which is to be compared with x. 
    @return A boolean represented as an int in C (false = zero, true = non zero). */
int sameSign(double x, double y){
  return (x > 0 && y > 0) || (x < 0 && y < 0);
}

/** Return the mid point in between x and y.
    @param[in] x The first double.
    @param[in] y The second double.  */ 
double middleOf(double x, double y){
  return x + (y - x)/2;
}

/** Is x considered to be very close to 0.0.
    @param[in] x The input to be evaluated.
    @return A boolean represented as an int in C (false = zero, true = non zero). */
int isSmallNumber(double x){
  return (fabs(x) < 0.0000001);
}   

/** Search for a root of the continuous function f between the parameters l and u.
   A root is a double r for which f(r) is very close to 0.0.
   @param[in] l The lower limit of the interval in which to search for a root.
   @param[in] u The upper limit of the interval in which to search for a root.
   @return The x-value r for which f(x) is close to zero.
   @pre The sign of f(l) and f(u) are different. */
double findRootBetween(double l, double u){
 while (!isSmallNumber(f(middleOf(l,u)))){ 
   if(sameSign(f(middleOf(l,u)), f(u)))
     u = middleOf(l,u);
   else 
     l = middleOf(l,u);
 }
 return middleOf(l,u);
}  

/** A sample interactive driver of the root searching for f. */ 
int main (void){
    double x, y;
    int numbers; 

    do{
      printf("%s","Find a ROOT between which numbers: ");
      numbers = scanf("%lf%lf", &x, &y);

      if (numbers == 2 && !sameSign(f(x),f(y))){
          double solution = findRootBetween(x,y);
          printf("\nThere is a root in %lf\n", solution);
        }
      else if (numbers == 2 && sameSign(f(x),f(y)))
          printf("\nf must have different signs in %lf and %lf\n",
                  x, y);
      else if (numbers != 2)
          printf("\nBye\n\n");
    }
    while (numbers == 2);

    return 0;
}

Henvisninger

Opgave 7.6. Kom godt i gang med Doxygen

Formålet med denne opgave er at sætte dig i gang med at bruge Doxygen til dokumentation af C programmer. Der findes også en video, som supplerer denne opgave.

Først skal du downloade Doxygen og installere programmet.

Hvis du ønsker diagrammer skal du også downloade Graphviz pakken, og installere denne.

Skriv nu et C program med dokumentationskommentarer, ligesom vist på rodsøgningsprogrammet. Start derefter Doxywizard, som giver dig mulighed for at sætte din dokumentation op. Der er en række forhold, som du skal være opmærksom på:

  • Under 'Step 1' skal du udnævne et katalog, hvor den såkaldte DoxyFile (med alle Doxygen's opsætningsparametre) gemmes.
  • Under 'Step 2' Projekt skal du udpege kataloget med C kildefilen og kataloget, hvori dokumentationen skal skrives
  • 'Step 2' Mode vælg All Entities som extraction mode og optimize for C.
  • 'Step 2' Diagram vælg Use dot tool from GraphViz hvis du ønsker avancerede diagrammer. Check Call graphs og Called by graphs, og uncheck de andre (i det mindste som en start).
  • I expert tabben er der et væld af options, hvor nogle allerede er sat op via de forrige punkter. For at få Doxygen og Graphviz til at arbejde sammen, bør du under Dot > Dot_Path udpege bin kataloget i din Graphviz installation.
  • Gem nu dit doxygen setup med File > Save. (Dette indebærer dannelsen af en såkaldt DoxyFile)
  • I Run tabben, aktiver Run doxygen knappen for at generere dokumentationen. Hold øje med evt. fejlmeddelelser i det store felt.

Doxygen er et meget rigt værktøj. Leg gerne med de mange muligheder for at tune dokumentationen til dine behov.


Debugging af C programmer

Debugging af C programmer
Slide Indhold Stikord
Referencer 

Debugging går ud på at finde årsagen til fejl i det kørende program

  • printf debugging

    • Indsættelse af tilstrækkelig mængde printf kald, som tilsidst afslører årsagen til fejl

    • Gradvis indsnævring af det sted i programmet, hvor fejlen gemmer sig

  • Brug af en debugger

    • Vi vil stifte bekendtskab med gbd - GNU's debugger

  • gdb

    • Command line debugging - "gammeldags"

    • Kraftig og meget alsidig!

    • Fordrer at programmet er compileret med henblik på debugging

      • gcc -g ...

Opgave 7.7. Debugging af findRootBetween

Denne opgave går ud på at debugge - at bruge gdb - på programmet der søger efter en rod i en kontinuert funktion. Se gerne først videoen om gdb, og følg også gerne dele af den tutorial som er knyttet til kurset.

Sæt et breakpoint når funktionen findRootBetween kaldes. Følg værdierne af de lokale variable l og u i takt med at while løkken udføres. Dette kræves at der sættes endnu et breakpoint.

Kør programmet igen, og break ligesom tidligere i findRootBetween. Sæt en watch på variable u, og følg med i hvordan u ændrer sig i løkken.

Leg gerne med andre aspekter af GDB med udgangspunkt i dette program.

Det vil naturligvis også være fint at anvende gdb på andre af de programmer, du har skrevet i kurset.

Flere opgaver
Slide Indhold Stikord
Referencer 

Opgave 7.8. Brug af assert i 'uger, dage, timer, minutter og sekunder' opgaven

I det velkendte program fra opgaven, der omregner et sekundtal til normaliserede uger, dage, timer, minutter og sekunder, er det attraktivt at sikre sig at de beregnede antal dage, timer, minutter og sekunder er inden for de forventede grænser (altså at de er normaliserede). Eksempelvis skal antallet af dage være mellem 0 og 6, og antallet af timer være mellem 0 og 23.

Tilføj et antal anvendelser af assert, som sikrer at de beregnede antal uger, dage, timer, minutter og sekunder er normaliserede.

Tilføj også et assert, der sikrer at det beregnede tidsdele 'kan regnes tilbage til' det oprindelige program input (det indlæste antal sekunder).

Hvis dit program er korrekt, hører du intet fra assert. Prøv derfor evt. at introducere et par bevidste fejl i dine beregninger, så du kan se hvordan assert reagerer på dette.


Samlede referencer
Indhold Stikord
Programbeskrivelse og programudførelse
Illstration af top-down programmering
Doxygen's DoxyFile
Dokumentation produceret af Doxygen

 

Kapitel 7: Fejl, Debugging, Test og Dokumentation
Kursets hjemmeside     Forfatteren's hjemmeside     Om frembringelsen af disse sider     Forrige lektion (top)     Næste lektion (top)     Forrige lektion (bund)     Næste lektion (bund)     
Genereret: 9. maj 2022, 13:59:22