IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 12 - Testování v jazyce C pokračování

V minulej lekcii, Testování v jazyce C, sme si povedali o testovaní v céčku a vyskúšali sme si takzvaný Glass-box Testing.

Dnes si povieme o Testami riadenom vývoji - (test-driven development - TDD).

TDD je technika tvorby softvéru, ktorá bola navrhnutá s cieľom umožniť čo najrýchlejšie zistenie a opravenie chýb.Spravidla sa testy píšu ešte pred samotnou implementáciou, ktorá musí pred odovzdaním všetky testy prejsť. Vytváraný kód sa tu testuje už od začiatku programovania, nie až po dokončení celého modulu. Tento prístup by teda mal obmedziť neskoré odstraňovanie chýb na minimum, čím sa ušetria vzácne zdroje – čas, pracovná sila aj peniaze.

Tento spôsob testovanie sa nazýva aj Black-box Testing, pretože modul (funkcia) je čierna skrinka a nevidíme do nej (niekedy ešte funkcia ani nie je napísaná).

Black-box Testing - Pokročilé konstrukce jazyka C

Parsing

Napadlo ma, že by sme si mohli napísať pomocnú knižnicu na parsovanie vstupov od užívateľa.

Tak ideme na to, nie? :-)

Založme si v IDE nový projekt s názvom Parsing, pridáme doňho header súbor parse.h a hlavný source súbor (obsahuje funkciu main()) premenujeme na parse.c.
Žiadny ďalší súbor nebudeme potrebovať, pretože použijeme podmienený preklad za pomoci funkcií preprocesora.

Súbor parse.h bude vyzerať takto:

#ifndef PARSING_H_INCLUDED
#define PARSING_H_INCLUDED
/**
* Funkcia skontroluje, či vstupný reťazec obsahuje iba číslice,
* ak nie, prvý odlišný znak zapíše na prvú pozíciu reťazca
* @param vstupný reťazec (vstupno - výstupný parameter)
* @return reťazec prevedený na int ak reťazec obsahuje iba číslice, inakšie 0
*/
int parse_int(char *number);
/**
* Funkcia skontroluje, či reťazec obsahuje iba jednu desatinnú bodku,
* ak ich obsahuje viac, vyhodnotí to ako chybu,
* skontroluje, či obsahuje namiesto botky čiarku, ak áno, prepíše ju na bodku,
* skontroluje, či vstupný reťazec obsahuje iba číslice (okrem jednej botky),
* ak nie, prvý odlišný znak zapíše na prvú pozíciu reťazca
* @param vstupný reťazec (vstupno - výstupný parameter)
* @return reťazec prevedený na double ak reťazec obsahuje iba číslice,
* alebo číslice a jednu bodku, inakšie 0
*/
double parse_double(char *number);
#endif // PARSING_H_INCLUDED

Súbor parse.c bude na začiatku vyzerať takto:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
#include "parsing.h"
#define TESTY // pri zakomentovaní budú testy odstavené

int parse_int(char *number)
{
    return atoi(number); // prevedie reťazec na int
}

#ifdef TESTY
#define TESTY

int main()
{
    char *cislo1;

    cislo1 = (char*)malloc(10);
    strcpy(cislo1, "123456");
    assert(parse_int(cislo1) == 123456);
    printf("Testy presli.\n");
    free(cislo);
}

#endif // TESTY

Uložiť, skompilovať a spustiť.

Konzolová aplikace
Testy presli.

=> testy prešli. A máme hotovo! :-)

Ak chcete správne pochopiť testovanie, bolo by dobré, keby ste všetky kroky robili spolu so mnou.

Skúsme pridať ďalší test:

cislo2 = (char*)malloc(10);
strcpy(cislo2, "123456x");
assert(parse_int(cislo2) == 0);

Výstup:

Konzolová aplikace
Assertion failed: parse_int(cislo2) == 0, file C:\Users\libcosenior\Documents\cecko\Parsing\parsing.c, line 26

This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.

A máme Assertion failed. Prečo? Reťazec obsahuje iný znak ako číslicu a návratová hodnota má byť 0 (nula).
Skúsme teda upraviť kód, napríklad takto:

if (atoi(number))
    return atoi(number);
else
    return 0;

A-a-a zase chyba. Čo sa stalo? Zistíme to tak, že si vypíšeme reťazec prevedený na číslo, pridáme výpis do funkcie main():

char *cislo2 = "123456x";
printf("retazec\t%s\ncislo\t%d\n", cislo2, atoi(cislo2));
assert(parse_int(cislo2) == 0);

Výstup:

Konzolová aplikace
retazec 123456x
cislo   123456
Assertion failed: parse_int(cislo2) == 0, file C:\Users\libcosenior\Documents\cecko\Parsing\parsing.c, line 27

A hneď vidíme kde je problém. Funkcia atoi() vzala do úvahy číslice, ktoré boli pred znakom x.
Ako to riešiť? Vyskúšame zmeniť hodnotu *cislo2 na "123x456" a otestujeme.

Výstup:

Konzolová aplikace
retazec 123x456
cislo   123
Assertion failed: parse_int(cislo2) == 0, file C:\Users\libcosenior\Documents\cecko\Parsing\parsing.c, line 27

Problém je tu stále. Funkcia atoi() prevedie číslice, ktoré sú pred iným znakom na číslo, preto teraz presunieme ten znak na začiatok reťazca: char *cislo2 = "x123456";

Výstup:

Konzolová aplikace
retazec x123456
cislo   0
Testy presli.

Testy prešli a teraz už vieme, že ak je v reťazci iný znak ako číslica, treba ho vložiť na začiatok reťazca.
Vyskúšajme upraviť funkciu takto:

for (int i = 0; i < strlen(number); i++) {
    if (!isdigit(number[i])) { // znak nie je číslica
        number[0] = number[i];
        break;
    }
}
if (atoi(number)) {
    return atoi(number);
}
else
    return 0;

Výstup:

Konzolová aplikace
retazec 123x456
cislo   123
Testy presli.

Čo sa deje? Prečo je číslo 123 a nie 0, pričom testy prešli? Všetko je v poriadku, len som zabudol zmazať (zakomentovať) riadok s výpisom a ten neberie výstupy z funkcie.

Fajn. Zdá sa, že prvá funkcia je bezchybná. Napriek tomu by možno bolo ešte dobré to poriadne otestovať napríklad tak, že do textového súboru napíšeme 100 rôznych reťazcov a tie sa budú postupne testovať.

RGR systém

Videli sme, že funkciu píšeme tak, aby prešla prvým testom skoro za každú cenu. Potom pridáme ďalší test a tak ďalej až do úspešného konca.

Tento systém sa nazýva RGR systém.

RGR systém - Pokročilé konstrukce jazyka C
  • RED - napíšte malý test, ktorý nefunguje a na prvýkrát možno ani nepôjde preložiť
  • GREEN - rýchlo spojazdnite test a neváhajte pri tom urobiť hocijakú somarinu
  • REFACTOR - zbavte sa všetkých duplicít vytvorených v snahe spojazdniť test

A tak stále dookola, kým nie je kód bezvadný.

Aby sme sa s tým lepšie stotožnili, TDD spôsobom si napíšeme aj druhú funkciu.

Pridáme si ju do súboru parsing.c:

double parse_double(char *number)
{

}

a napíšeme prvý test.

/** testy funkcie int parse_double(char *number) **/
char *cislo3;

cislo3 = (char*)malloc(10);
strcpy(cislo3, "123.456");
assert(parse_double(cislo3) == 123.456);

Nedá sa to scompilovať, pretože vo funkcii nemáme návratovú hodnotu. Doplníme.

return atof(number);

Skompilujeme a spustíme. Prešli. Máme hotovo! :-)

Samozrejme, že nie. Ideme nájsť ďalšie špeciálne zadanie od užívateľa.

strcpy(cislo3, "1.23.45.6");
assert(parse_double(cislo3) == 0);

Assertion failed! V reťazci môže byť okrem číslic maximálne jedna bodka (tečka). Čo s tým?

int point = 0;
for (int i = 0; i < strlen(number); i++) {
    if (number[i] == '.')
        point++;
}
if (point > 1)
    return 0;
else
    return atof(number);

Testy prešli. Už zase máme hotovo. :-` Ďalšia užívateľova špecialitka:

strcpy(cislo3, "123,456");
assert(parse_double(cislo3) == 123.456);

V tomto prípade chceme, aby to vzalo aj tú čiarku namiesto botky.
Assertion failed!
Odstránime chybu tým, že doplníme do funkcie.

for (int i = 0; i < strlen(number); i++) {
    if (number[i] == ',')
        number[i] = '.';
}

Testy prešli. Skúsme toto:

strcpy(cislo3, "123,45,6");
assert(parse_double(cislo3) == 0);

Assertion failed! Prečo? Vo funkcii treba najskôr ošetriť čiarky a až potom botky. A bude to v poriadku.

Ďalšie špatné zadanie môže byť, že tam budú znaky mimo číslic, čiarok a bodiek.

strcpy(cislo3, "12x3.45,6");
assert(parse_double(cislo3) == 0);

Samozrejme, že je zase chyba. Vyriešime pridaním kódu:

for (int i = 0; i < strlen(number); i++) {
    if (!isdigit(number[i])) {
        number[0] = number[i];
        break;
    }
}

Testy prešli.
Ale skúsme ešte niečo:

strcpy(cislo3, "12x3456");
assert(parse_double(cislo3) == 0);

Nebude tam žiadna bodka, čo to spraví? Testy prešli.

Fajn. Zdá sa, že aj druhá funkcia je bezchybná. Napriek tomu by možno bolo ešte dobré to poriadne otestovať napríklad tak, že do textového súboru napíšeme 100 rôznych reťazcov a tie sa budú postupne testovať.

Pozor dôležité!
Nikdy nezabudnite nakoniec upraviť kód tak, aby bol čo najjednoduchší, aby sa neopakovali rovnaké časti kódu a aby bol dobre okomentovaný. (Môj je priložený.)

Teraz môžeme vypnúť testovanie tak, že zakomentujeme riadok:

//#define TESTY // pri zakomentovaní budú testy odstavené

a nepodarí sa nám to ani skompilovať, pretože projektu chýba funkcia main().
To nateraz nevadí. Niekedy, keď budete tieto dve funkcie potrebovať, stačí do projektu pridať obidva súbory a v súbore, v ktorom sa budú funkcie používať, includovať parsing.h.

Takže ako vidíte, testovanie nás núti premýšľať aké všemožné vstupy sa môžu do funkcie dostať a aké to môže mať následky na beh programu a núti nás to upraviť kód tak, aby sme všetky problémy už dopredu vyriešili.

Samozrejme že, vývoj riadený testami určite nie je univerzálnym liekom na všetko a dosiahnutie želaného efektu v žiadnom prípade nie je zaručené. Ale je to najlepšia cesta, ako sa maximálne vyhnúť možným problémom s nefunkčnosťou projektov u konečného užívateľa.

Keď sa nad tým zamyslím, vlastne vývoj všetkého sa testuje. Počnúc hračkami pre deti, cez napríklad bielu techniku, autá, zbrane až po rakety. Kto netestuje, väčšinou predáva nekvalitné šunty a preto rýchlo skončí svoju kariéru.

Ja vám všetkým držím palce pri písaní svojich projektov pomocou TDD.

Na záver prikladám zdrojové kódy napísané v Linux Ubuntu.

V budúcej lekcii, Funkce s variabilním počet a typem argumentů v jazyce C, si ukážeme ako deklarovať funkciu s variabilným počtom a typom argumentov.


 

Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.

Stáhnout

Stažením následujícího souboru souhlasíš s licenčními podmínkami

Staženo 112x (9.15 kB)
Aplikace je včetně zdrojových kódů v jazyce c

 

Předchozí článek
Testování v jazyce C
Všechny články v sekci
Pokročilé konstrukce jazyka C
Přeskočit článek
(nedoporučujeme)
Funkce s variabilním počet a typem argumentů v jazyce C
Článek pro vás napsal Libor Šimo (libcosenior)
Avatar
Uživatelské hodnocení:
6 hlasů
Obľúbil som si jazyk C a snažím sa ho spoznať čo najhlbšie. Začal som používať c# WPF, je to krása.
Aktivity