Elementi di
programmazione
ad oggetti
a. a. 2009/2010
Corso di Laurea Magistrale in Ingegneria Elettronica
Docente: Mauro Mazzieri, Dipartimento di Ingegneria Informatica,
Gestionale e dell’Automazione
Lezione 11
Unit Testing e Test Driven Development
Che cos’è il testing?
I programmi possono contenere errori
 Un test consiste nell’esecuzione di un
frammento di codice per verificare
che funzioni o che continui a
funzionare dopo una modifica

I test hanno lo scopo di trovare errori in
un programma
 Testare un programma è il modo più
affidabile per confermare che funzioni e
che continui a funzionare dopo che è
stato modificato

Modalità di testing




Si eseguono molti test, ciascuno su singole
porzioni del programma
Occorre scegliere accuratamente le porzioni
di codice da andare a testare, privilegiando
le parti in cui è più probabile che vi siano
errori
Per ciascuno occorre scegliere
accuratamente i dati di input, in quanto
non è possibile testare tutte le possibili
combinazioni di input
Se l’output differisce da quello atteso, si è
verificato un errore, solitamente nella parte
di codice in esame
Come vengono eseguiti i test

Dovrebbe essere possibile eseguire
spesso i test
Dunque, è necessario che siano eseguiti
velocemente
 … quindi, bisogna automatizzarli


Gli unit test servono a verificare il
comportamento di un oggetto
Dunque, verificano l’implementazione
della classe di cui l’oggetto è istanza
 L’idea è di chiamare un metodo e
verificare la correttezza del risultato

Altri principi dello Unit Testing
I test devono essere ripetibili
 I test devono essere indipendenti, in
modo che l’esito di un test non
influisca sull’esito degli altri
 Bisogna eseguire i test dopo ogni
cambiamento
 Se un test fallisce, affrontare subito il
problema

Esempio: classe Stack
#include <vector>
template<class T>
class Stack
{
std::vector<T> data;
public:
void size() const {
return data.size();
}
void push(const T& t) {
data.push_back(t);
}
T pop() {
T result = data.back();
data.pop_back();
return result;
}
};
Esempio: testiamo la classe Stack
#include "Stack.h"
#include <iostream>
int main() {
Stack<int> s;
s.push(3);
s.push(4);
std::cout << s.pop() << "
s.pop() << std::endl;
system("PAUSE");
return 0;
}

Ci aspettiamo produca in output:
3 4
" <<
Framework per lo unit testing
Un framework per lo unit testing è
una collezione di classi che consente
di eseguire unit test
 Il primo fu SUnit (per Smalltalk)
 Ogni linguaggio ha il suo “xUnit”

Per Java c’è JUnit
 Per .NET c’è nUnit


Per il C++ ce ne sono diversi,
nessuno ottimale…
Esempio: test di Stack con CppUnitLite
#include "TestHarness.h"
#include "Stack.h"
TEST( creation, Stack )
{
Stack<int> s;
LONGS_EQUAL(0, s.size());
}
TEST( pushpop, Stack )
{
Stack<int> s;
long a = 3;
s.push(a);
long b = s.pop();
LONGS_EQUAL(a, b);
}
Esempio: test di Stack con CppUnitLite
#include "TestHarness.h"
int main() {
TestResult tr;
TestRegistry::runAllTests(tr);
system("PAUSE");
return 0;
}

Produce in output:
There were no test failures
Struttura di un test

Un test viene creato con la macro
TEST
Viene generata una classe
 Gli identificatori tra parentesi sono il
nome del test ed il nome del gruppo

È
possibile seguire separatamente solo i test
di un certo gruppo

Nel test viene istanziata la classe da
testare

Le verifiche vengono compiute
attraverso le macro CHECK,
LONGS_EQUAL, DOUBLES_EQUAL, FAIL
Test Driven Development (TDD)

Strategia di sviluppo costruita sull’uso
degli unit test

Progettare la caratteristica da
implementare
 Se
è grande dividerla in parti più piccole
Scrivere il test
 Scrivere il codice
 Eseguire tutti i test

i test passano, effettuare il refactoring
 Se un test fallisce risolvere il problema
immediatamente
 Se
Refactoring

Il refactoring è il processo di pulizia del
codice che ne migliora la struttura interna
senza alterarne il comportamento esterno


Per esempio, il refactoring di un metodo porta
ad un metodo con lo stesso nome, parametri e
tipo di ritorno, ma diverso algoritmo o diversa
implementazione dello stesso algoritmo
Il refactoring è essenziale per il TDD




La prima versione del codice può superare i test ma
essere di scarsa qualità
Da’ltra parte, prima di effettuare il refactoring il
codice deve essere funzionante
Esistono interi cataloghi di refactoring comuni: extract
method, rename ecc.
Problema comune: duplicazione di codice
Red, Green, Refactor

Scrivere il test


Scrivere quel tanto di codice che basta per
compilare



Eseguire il test: il test fallisce
Scrivere il codice nella maniera più
semplice possibile, il minimo indispensabile
per superare il test


Serve a specificare cosa il codice dovrebbe fare
Eseguire tutti i test
e accertarsi che tutti i test passano
Eseguire il refactoring
Ripetere per il prossimo test
Esempio estremo: fattoriale











TEST( test0, fattoriale ) { LONGS_EQUAL(1, f(0)); }
int f(int n) { return 1; }
TEST( test1, fattoriale ) { LONGS_EQUAL(1, f(1)); }
TEST( test2, fattoriale ) { LONGS_EQUAL(2, f(2)); }
int f(int n) { return n <= 1 ? 1 : 2; }
TEST( test3, fattoriale ) { LONGS_EQUAL(6, f(3)); }
int f(int n) { return n <= 1 ? 1 : (n <= 2 ? 2 : 6); }
int f(int n) { return n <= 1 ? 1 : n * f(n-1) ; }
TEST( test7, fattoriale ) { LONGS_EQUAL(5040, f(7)); }
TEST( test11, fattoriale ) { LONGS_EQUAL(39916800, f(11));
}
Casi problematici:


f(-1) ?
f(100) ?
Scarica

Lezione 11