Index over opgaver i denne lektion   Alfabetisk indeks   Kursets hjemmeside   

Opgaver og løsninger
Fejl, Debugging, Test og Dokumentation


7.1   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);
}

Løsning

Problemet er at udskriften kan vises to eller flere gange, afhængig af hvor mange gange brugeren bliver bedt om at genindlæse de to tal.

Løsningen er det sidste af de viste programmer på sliden.


7.2   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:

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.

Løsning

Her er min løsning (herunder en kommentar der viser compileringen):

/* Compilation:
  gcc -c CuTest.c
  gcc tms-test.c CUTest.o
*/

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

#define SECONDS_PER_MINUTE 60
#define SECONDS_PER_HOUR (SECONDS_PER_MINUTE * 60)

void timer_minutter_sekunder(int input_sekunder, int *out_timer, int* out_minutter, int* out_sekunder);

CuSuite* SekunderGetSuite(void);

void test_tms_4000(CuTest *tc){
  int t, m, s;
  timer_minutter_sekunder(4000, &t, &m, &s);
  CuAssert(tc, "Test with 4000 seconds", t == 1 && m == 6 && s == 40);
}  

void test_tms_75(CuTest *tc){
  int t, m, s;
  timer_minutter_sekunder(75, &t, &m, &s);
  CuAssert(tc, "Test with 75 seconds", t == 0 && m == 1 && s == 15);
}  

void test_tms_3700(CuTest *tc){
  int t, m, s;
  timer_minutter_sekunder(3700, &t, &m, &s);
  CuAssert(tc, "Test with 3700 seconds", t == 1 && m == 1 && s == 40);
}  

void test_tms_55(CuTest *tc){
  int t, m, s;
  timer_minutter_sekunder(55, &t, &m, &s);
  CuAssert(tc, "Test with 55 seconds", t == 0 && m == 0 && s == 55);
}  

void test_tms_3661(CuTest *tc){
  int t, m, s;
  timer_minutter_sekunder(3661, &t, &m, &s);
  CuAssert(tc, "Test with 3661 seconds", t == 1 && m == 1 && s == 1);
}  

CuSuite* SekunderGetSuite(void) {
  CuSuite* suite = CuSuiteNew();
  SUITE_ADD_TEST(suite, test_tms_4000);
  SUITE_ADD_TEST(suite, test_tms_75);
  SUITE_ADD_TEST(suite, test_tms_3700);
  SUITE_ADD_TEST(suite, test_tms_55);
  SUITE_ADD_TEST(suite, test_tms_3661);
  return suite;
}

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

  RunAllTests();
  
  return EXIT_SUCCESS;
}

void timer_minutter_sekunder(int input_sekunder, int *out_timer, int* out_minutter, int* out_sekunder){ 
  int rest_sekunder;

  *out_timer = input_sekunder / SECONDS_PER_HOUR;
  rest_sekunder = input_sekunder  % SECONDS_PER_HOUR;
  *out_minutter = rest_sekunder / SECONDS_PER_MINUTE;
  rest_sekunder = rest_sekunder  % SECONDS_PER_MINUTE;
  *out_sekunder = rest_sekunder;
}


7.3   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.


7.4   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).


7.5   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å:

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


7.6   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.


7.7   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.

Løsning

Her er min løsning:

#include <stdio.h>
#include <assert.h>

#define MINUTES_PER_HOUR 60
#define HOURS_PER_DAY 24
#define DAYS_PER_WEEK 7
#define SECONDS_PER_MINUTE 60
#define SECONDS_PER_HOUR (SECONDS_PER_MINUTE * MINUTES_PER_HOUR)
#define SECONDS_PER_DAY (SECONDS_PER_HOUR * HOURS_PER_DAY)
#define SECONDS_PER_WEEK (SECONDS_PER_DAY * DAYS_PER_WEEK)

int main(void){
  int input_seconds,
      weeks, days, seconds, minutes, hours, rest_seconds;

  /* Prompt for input: */
  printf("Read a non-negative number of seconds: ");
  scanf("%d", &input_seconds);

  /* Calculate the weeks */
  weeks = input_seconds / SECONDS_PER_WEEK;
  rest_seconds = input_seconds % SECONDS_PER_WEEK;

  /* Calculate the days (in the interval 0..6) */
  days = rest_seconds / SECONDS_PER_DAY;
  rest_seconds = rest_seconds % SECONDS_PER_DAY;           /* this is a new rest_seconds. The rest_seconds variable is reused */
  assert(days >= 0 && days < DAYS_PER_WEEK);

  /* Calculate the hours (in the interval 0..23) */
  hours = rest_seconds / SECONDS_PER_HOUR;
  rest_seconds =  rest_seconds % SECONDS_PER_HOUR;
  assert(hours >= 0 && hours < HOURS_PER_DAY);

  /* Calculate the minutes (in the interval 0..59) and the remaining seconds (in the interval 0..59) */
  minutes = rest_seconds / SECONDS_PER_MINUTE;
  seconds = rest_seconds % SECONDS_PER_MINUTE;
  assert(minutes >= 0 && minutes < MINUTES_PER_HOUR);
  assert(seconds >= 0 && seconds < SECONDS_PER_MINUTE);

  assert(input_seconds == weeks * SECONDS_PER_WEEK + days * SECONDS_PER_DAY + hours * SECONDS_PER_HOUR + minutes * SECONDS_PER_MINUTE + seconds);

  /* Print the results: */
  printf("%d seconds correspond to %d weeks, %d days, %d hours, %d minutes and %d seconds\n",
         input_seconds, 
         weeks, days, hours, minutes, seconds);

  return 0;
}

Bemærk lige, at det er blevet attraktivt at tilføje endnu flere symbolske konstanter.


Genereret: Torsdag 28. oktober 2021, 08:27:36