Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Info

Ciele cvičenia

Cvičenie je zamerané na modernú techniku vývoja aplikácii postavenú na báze testovania správania sa funkcií. Tento prístup k vývoju sa nazýva vývoj riadený testami (test driven development), kedy sa voči známemu rozhraniu najprv napíšu testy a až tak sa vytvára samotná implementácia.

Po osvojení si učiva, študent dokáže navrhnúť vlastné testy, ktorými je následne schopný testovať vlastné funkcie, ktoré práve vyvíja. Zároveň tento testami riadený vývoj dokáže implementovať v nástroji make a dokáže k nemu vytvoriť adekvátny konfiguračný súbor Makefile.

  • Vývoj riadený testami
  • Implementácia testov s využitím knižnice assert.h a nástroja make
Info

Odporúčaná literatúra a dôležité odkazy

[1] KPI-FEI-TUKE: Test-Driven Development

[2]  Dubey, V. : Test Driven Development — Understanding the business better

[3]  Woestenburg, A., Baccus, S. : Test-Driven Development

[4] Kováč, O.: ZAP - Cvičenie č. 4

[5] : www.tutorialspoint.com: C library macro - assert()

[6] Kováč, O.: PROG - Cvičenie č. 1 - Modulárne programovanie

[7] KPI-FEI-TUKE: Video na 2. cvičenie


Vývoj riadený testami

Ide o modernú techniku vývoja aplikácii postavenú na báze testovania správania sa funkcii. Tento prístup k vývoju sa nazýva vývoj riadený testami (test driven development -TDD), kedy sa voči známemu rozhraniu najprv napíšu testy a až tak sa vytvára samotná implementácia [1-3]. Z uvedeného vyplýva, že riešenie zadaného problému nezačína písaním kódu, ako sú študenti zvyčajne (nesprávne) zvyknutí, ale začína sa stanovením požadovaných funkcionalít, vstupov a očakávaných výstupov. Pre napĺňanie týchto požiadaviek sa najprv napíše test (stretávame sa aj s pojmom unit test) a až následne sa napíše kód samotného programu. 

Vývoj programov vo filozofii TDD pozostáva z piatich krokov:

1. Vytvorenie testu

Prvým krokom v TDD je napísanie testu, ktorý bude overovať funkcionalitu vyvíjaného programu. V podstate v tomto kroku sa stanovia požiadavky na funkcionalitu programu. Do istej miery je to veľmi podobné zásadám, ktoré sme sa učili na ZAPku [4]. Je potrebné stanoviť, aké budú vstupy a aké budú výstupy vyvíjaných funkcií a programov. Pre zjednodušenie a lepšie porozumenie problematike, budeme ďalej uvažovať iba o funkcionalitách (moduloch - pozri [6]) a nie komplexných programoch.

2. Spustenie testu (prvý krát nesmie prejsť)

Potom, ako je napísaný test pre daný modul programu, je potrebné napísať kód pre samotný modul. "Prvý krát by tento mal pri testovaní zlyhať". Tomuto kroku treba rozumieť tak, že je potrebné si pripraviť kostru daného modulu, ktorá vráti nejakú preddefinovanú hodnotu alebo nevráti nič a testom tak neprejde. Nesmie sa však stať, že problém nastane pri kompilácii! 

3. Napísanie vlastného kódu a jeho testovanie 

V tomto kroku sa prechádza k vývoju a programovaniu daného modulu a jeho funkcionalít. Tento vyvíjaný kód sa neustále kompiluje a testuje. Ak je test neúspešný, je potrebné daný kód upraviť a spustiť ďalší test. Ak vyvinutý modul prejde testom, prechádza sa do ďalšieho kroku.

4. Refaktorizácia - postupné vylepšovanie

Kód prechádza testami a robí to, čo sa od neho očakáva. V tomto okamihu je možné začať premýšľať nad tým, ako by sa daná funkcionalita dala vylepšiť, zefektívniť, rozšíriť a podobne. Stanovia sa teda nové ciele, ktoré treba implementovať do testov. Prechádza sa k poslednému kroku - "uprataniu" 

5. Upratovanie a návrat k bodu 1

Pred návratom k bodu 1 je potrebné vymazať objektové a spustiteľné súbory, ktoré boli v priebehu vývoja skompilované. 


Implementácia testov s využitím knižnice assert.h a nástroj make

Na prvý pohľad sa TDD možno zdá trochu "exoticky" ale test vo svojej podstate je len ďalší program, ktorý obsahuje funkciu main, v ktorej sú volané funkcie testovaného modulu a tieto sú vyhodnocované. Vyhodnocované funkcie by mali mať vstupné parametre ako aj návratové hodnoty. Tento typ funkcii je možné vyhodnocovať pomocou makra assert, ktorá sa nachádza v knižnici assert.h [5].  Makro assert pokiaľ jeho argument je pravdivý (TRUE) nespraví nič a kód testu pokračuje na ďalší riadok. V prípade, že argument bude nepravdivý, vygeneruje sa chyba priamo do stderr (standard error stream to display error messages and diagnostics). Pokiaľ je test vykonávaný v nástroji make, dôjde k zastaveniu kompilácie s chybou! Pokiaľ máte rezervy v problematike nástroja make, dôrazne odporúčam zopakovať si predchádzajúce cvičenie [6].

Úloha 1.

Vytvorte modul pre základné aritmetické operácie súčet, rozdiel, súčin, podiel, modulo, inverzná hodnota. Tento modul vyvíjajte v súlade s filozofiou TDD! Použite pritom poskytnutú kostru programu.

  • hlavny.c , ktorý obsahuje iba funkciu main si vytvorte tak, aby sa dal skompilovať. V princípe nemusí obsahovať nič okrem return 0.
  • test_funkcie.c obsahuje testy jednotlivých funkcionalít programu
  • funkcie.c obsahuje samotné funkcie, ktoré sú deklarované vo funkcie.h


Code Block
languagecpp
titlefunkcie.h
linenumberstrue
/*
  Deklarácie základných matematických operácií pre CV4 z predmetu PROG
*/

int sucet(int, int);  // súčet
int rozdiel(int, int);// rozdiel
int sucin(int, int);  // súčin
int podiel(int, int); // podiel
int modulo(int, int); // výpočet celočíselného zvyšku po delení
int inverz(int, int); // násobenie s -1
Code Block
languagecpp
titlefunkcie.c
linenumberstrue
// Definície základných matematických operácií pre CV4 z predmetu PROG

#include "funkcie.h"
int sucet(int a, int b){
  return a + b;
}
int rozdiel(int a, int b){
  return a - b + 10; // toto nie je veľmi rozumné ...
}
// Tu majú byť ostatné definície
Code Block
languagecpp
titletest_funkcie.c
linenumberstrue
#include <stdio.h>
#include "funkcie.h"
#include <assert.h>

int main()
{
    printf("\n Testy pre funkcionalitu sucet:");
    assert(sucet(1,1) == 2);             
    printf("\n sucet(1,1) == 2 : PASS");  
    assert(sucet(2,2) == 4);
    printf("\n sucet(2,2) == 4 : PASS");
    assert(sucet(0,2) == 2);
    printf("\n sucet(0,2) == 2 : PASS");
    
    // TU treba dokončiť ostatné testy - aspoň 3 testy na každú funkciu
    
    // Toto je demoštrácia testu ktorý nemá prejsť!
    printf("\n Testy pre funkcionalitu rozdiel:");
    assert(rozdiel(0,2) == -2); // rozdiel má vrátiť -2, ale je tam chyba
    printf("\n rozdiel(0,2) == -2 : PASS");
    
    printf("\n >>>>> All tests PASSED! <<<<< \n");
    return 0;
}
Code Block
languagebash
titleMakefile
linenumberstrue
CC= gcc
CFLAGS= -Werror -Wall
OUTPUT= PROGRAM
# Všimnime si, že predtým než sa skompiluje hlavny.c je spustený test!
all:  funkcie.o hlavny.o test_funkcie
	./test_funkcie
	gcc $(CFLAGS) hlavny.o funkcie.o -o $(OUTPUT)
	@echo "Skompilovaný celý program"

funkcie.o: funkcie.c
	@gcc -Werror -Wall funkcie.c -c
	@echo "Skompilovane iba funkcie.o"

hlavny.o: hlavny.c
	@$(CC) $(CFLAGS) hlavny.c -c
	@echo "Skompilovane iba hlavny.o"

# Toto je skompilovanie testu pre modul funkcie
test_funkcie: test_funkcie.c funkcie.o
	@$(CC) $(CFLAGS) test_funkcie.c funkcie.o -o test_funkcie

clean:
	rm *.o $(OUTPUT) test_funkcie
	@echo "Vymazalo sa vsetko co bolo potrebne vymazat"




Testujeme a opravujeme!

Pri testovaní s využitím funkcie assert existujú len dva možné výsledky:

Test neprejde: Po spustení make all dôjde pri pri vykonávaní testu pomocou programu ./test_funkcie k chybe. To bude mať za následok, že sa kompilácia skončí a výsledný PROGRAM sa neskompiluje!

Code Block
languagebash
ab123cd@zapfei:$ make all
Skompilovane iba funkcie.o
Skompilovane iba hlavny.o
./test_funkcie

 Testy pre funkcionalitu sucet:
 sucet(1,1) == 2 : PASS
 sucet(2,2) == 4 : PASS
 sucet(0,2) == 2 : PASS
test_funkcie: test_funkcie.c:19: main: Assertion `rozdiel(0,2) == -2' failed.
make: *** [Makefile:6: all] Aborted (core dumped)




Test prejde: Po spustení make all  pri vykonávaní testu pomocou programu ./test_funkcie nedôjde k žiadnej chybe. Kompilácia výsledného programu PROGRAM sa úspešne vykoná! (predtým opravíme funkciu rozdiel)

Code Block
languagebash
ab123cd@zapfei:$ make all
Skompilovane iba funkcie.o
Skompilovane iba hlavny.o
./test_funkcie

 Testy pre funkcionalitu sucet:
 sucet(1,1) == 2 : PASS
 sucet(2,2) == 4 : PASS
 sucet(0,2) == 2 : PASS
 Testy pre funkcionalitu rozdiel:
 rozdiel(0,2) == -2 : PASS
 >>>>> All tests PASSED! <<<<<
gcc -Werror -Wall hlavny.o funkcie.o -o PROGRAM
Skompilovaný celý program


Testujeme a vylepšujeme!

Pokiaľ sme úspešne prešli testom pre funkciu sucet() a opravili sme funkciu rozdiel(), môžeme pristúpiť k návrhu testov pre ostatné funkcie a ich následnú implementáciu. Predtým je však potrebné "upratať". K tomuto účelu použijeme make clean, ktorý vymaže všetky objektové súbory, spustiteľné testy a tiež súbor PROGRAM.


Úloha 2.

Pokúste sa vykonať vývoj riadený testami na svojom zadaní TOP SECRET! Hoci už ho máte odovzdaný a ohodnotený, neznamená to, že si nemôžete upraviť Makefile a vyskúšať si TDD. Už pre istotu ale nerobte commity (pokiaľ máte dobré hodnotenie na aréne).



K cvičeniu je odporúčané pozrieť si aj oficiálne video

Widget Connector
width500
urlhttps://www.youtube.com/watch?v=52kBAlhMc5k&ab_channel=Programovanie
height400