• Non ci sono risultati.

Testing e Debugging del Software

N/A
N/A
Protected

Academic year: 2021

Condividi "Testing e Debugging del Software"

Copied!
59
0
0

Testo completo

(1)

Testing e Debugging del Software

Michelangelo Diligenti Ingegneria Informatica e

dell'Informazione

diligmic@dii.unisi.it

(2)

Sommario

Testing

La correttezza del software

Tipi di test

Esercizi con libreria gtest

Debugging

Tecniche

Strumenti

gdb

valgrind

Esercizi

(3)

Testing di correttezza

Analisi di correttezza di un modulo o intero sistema

Esecuzione di singoli casi (test case)

Confronto risultato con valore atteso

Esecuzione non corretta risulta in failure

Permette di scoprire gli errori (bugs/defect/fault)

Porta il software ad un livello di qualità accettabile

Non vuol dire perfezione

(4)

La correttezza del software

Assiomi del testing

Non possibile testare un programma in modo completo

Dimostrare correttezza vuol dire testare tutte le coppie input/output

Input space troppo ampio

Stato interno dei programmi ampio

Numero di possibili esecuzioni enorme

Specifiche sono spesso interpretabili in modo soggettivo

(5)

La correttezza del software

Esempio, dimostrare correttezza di

int func(int x, int y) { return x + y; }

Necessario testare tutte le combinazioni (x,y)

Se un int è 4 byte

232 * 232 = 264 casi da testare!

(6)

La correttezza del software

Se si testano solo alcuni input, spesso non possibile testare tutti i cammini (path) che segue il codice

int func(int x, int y) {

for (int i = 0; i < n; ++i) if (a[i] == b[i/2])

a[i] += 100;

else

b[i] /= 2;

}

a e b vettori di dimensione 100

2 path ad ogni loop, ma il path scelto influenza i path successivi

Numero path possibili sull'intera esecuzione è 2n

(7)

La correttezza del software

Software testing è un processo risk-based

Più tests ci sono più probabile che il software sia corretto

Non possibile provare correttezza

Possibile ci siano altri bug

Più bug sono stati trovati, più probabile ce ne siano altri

In generale, necessario trovare un compromesso tra costo del testing e benefici attesi

(8)

La correttezza del software

Software testers

Spesso non amati come componenti dei teams

Chi programma pensa che blocchino sviluppo e creatività

Non vero!

Testing richiede creatività e professionalità, oltre capacità ben specifiche

Senza competenze specifiche impossibile creare test efficaci nel scovare bugs

(9)

Tipi di test

Unit testing: test di singoli moduli software (spesso una singola classe)

Black box testing: basati sulle specifiche. In caso di classi testando l'interfaccia pubblica della classe

White box testing: basati sulla logica interna. Il test è friend del test

Gray box testing: una mistura dei precedenti

Integration testing: test dell'integrazione tra moduli

System testing: test a livello di sistema

(10)

Tipi di test

Performance testing: test del tempo e risorse necessarie per svolgere un certo task

Stress testing: test nel caso di chiamate ripetute o concorrenti ad un certo task

Fondamentale per codice che gira su servers

Regression testing: controlla che un cambiamento nel codice non introduce nuovi bug o problemi

Spesso si basa su unittesting

Ma può essere necessario aggiungere integration e performance testing

(11)

Unittesting

Meccanismo di basso livello ma fondamentale per il successo del testing

Importante che ogni modulo e classe abbia test associato

Basato su un insieme di test cases

Primo passo per realizzare Regression Testing

Disponibili librerie per supportare l'implementazione ed esecuzione degli unittest

Dette Test Management Libraries

Le studieremo

(12)

Integration e System testing

Realizzabile bottom-up

Si testano i singoli moduli con unittesting e poi gruppi di moduli sempre più grandi fino all'intero sistema

Top-down

Si testa l'intero sistema e poi gruppi di moduli fino ai singoli moduli con unittesting

(13)

Test nello sviluppo del software

Test processo complesso che va dal design, all'implementazione ed all'esecuzione

Test sono eseguiti durante l'implementazione di una classe/modulo per controllare stato

Sempre prima di effettuare svn commit!

Test Design Test

Implementation Test

Execution Results Verification

Test Management Library

(14)

Test ed automazione

Consigliabile automatizzare l'esecuzione dei test

Eseguiti in modo regolare automaticamente per controllare stato del codice nel repository

Possibile anche automatizzare eseguzione test in svn, evita commit di codice che rompe i test

Test Design Test

Implementation Test

Execution Results Verification

Automatizzazione

Test Management Library

(15)

gtest

Libreria open-source in C++ inizialmente realizzata da Google

Detta googletest o gtest

Scaricabile da

http://code.google.com/p/googletest/

Utilizzabile liberamente in ogni contesto (anche industriale)

Rende facile la creazione di test ed il loro monitoraggio

(16)

gtest

Google Test si basa sul concetto di assertion, controllo che una condizione sia verificata

Assertion può essere fatale (fatal), se blocca

esecuzione del test o non fatale se il test continua

Se possibile, usare le non fatali, il programma continua e si ottiene sommario finale dell'andamento del test

Test è un insieme di assertions

Test case è un gruppo di test che condividono strutture dati e concetti

Programma di test contiene più test cases

(17)

gtest e test case

Test è una funzione

TEST(NomeTestCase, NomeTest) { … }

Più test associati a stesso test case, formano un test case

TEST(NomeTestCase, NomeTest1) { … } TEST(NomeTestCase, NomeTest2) { … }

TEST(NomeTestCase, NomeTestN) { … }

Intero programma è test program

(18)

gtest e assertions

Vi sono tanti modi di scrivere assertions, prendono due argomenti che devono rispettare la condizione

Fatali

ASSERT_TRUE(bool);

ASSERT_FALSE(bool);

Non fatali

EXPECT_TRUE(bool);

EXPECT_FALSE(bool);

Esempio

ASSERT_TRUE(ptr != NULL);

(19)

gtest e assertions

Vi sono tanti modi di scrivere assertions, prendono due argomenti che devono rispettare la condizione

Fatali

ASSERT_EQ(arg1, arg2)

ASSERT_GT(arg1, arg2)

ASSERT_GE(arg1, arg2)

ASSERT_NE(arg1, arg2)

Non fatali

EXPECT_EQ(arg1, arg2)

EXPECT_GT(arg1, arg2)

EXPECT_GE(arg1, arg2)

EXPECT_NE(arg1, arg2)

(20)

gtest e main

Il main del test deve chiamare le funzioni

::testing::InitGoogleTest(&argc, argv);

return RUN_ALL_TESTS();

La seconda funzione esegue tutti i test

(21)

gtest: esempio Test

TEST(IntegerTest, OperatorIncrement) { Integer i(5);

++i;

EXPECT_EQ(6, i.Get());

}

Vediamo ora l'implementazione completa di un test:

operator_test.cc

(22)

gtest: white box testing

White box testing richiede che test sia friend

gtest permette questo nel seguente modo

nella definizione della classe testata

#include "gtest/gtest_prod.h" // necessario includere gtest!

class ClasseDaTestare {

FRIEND_TEST(NomeTestCase, NomeTest);

… /* implementazione classe */

};

nel test

TEST(NomeTestCase, NomeTest) { ClasseDaTestare c;

EXPECT_EQ(0, c.a); // a dato privato di c

(23)

gtest e classi

Talvolta ci sono operazioni da fare all'inizio e fine di ogni test

Ripeterle ogni volta poco elegante

Se non fatte si rischia che il test non sia valido

gtest permette di definire queste operazioni in modo consistente

Test Fixture: classe figlia di ::testing::Test;

Contiene oggetti che si vuole usare per composizione

Implementare metodo SetUp per definire cosa fare ad inizio test

Implementare metodo TearDown per definire cosa fare a fine test

Chiamare TEST_F() per fare test usando Fixture

(24)

gtest e Test Fixture: esempio

class IntegerTestWithFixture : public ::testing::Test { protected:

Integer i;

virtual void SetUp() { // chiamato prima di ogni test i = Integer(5);

}

// virtual void TearDown() {} // chiamato dopo ogni test };// Lista di singoli Test

TEST_F(IntegerTestWithFixture, Constructors) { EXPECT_EQ(5, i.Get());

}TEST_F(IntegerTestWithFixture, OperatorEqual) { Integer j(0);

j = i;

EXPECT_EQ(5, j.Get());

(25)

gtest e Test Fixture

Anche con Fixture possibile dichiarare i singoli test friend nella classe da testare

Oppure dichiarare l'intera classe come friend

class Integer {

friend class IntegerTestWithFixture;

// Implementazione della classe }

(26)

gtest e classi

Talvolta ci sono operazioni da fare all'inizio e fine di ogni test

Ripeterle ogni volta poco elegante

Se non fatte si rischia che il test non sia valido

gtest permette di definire queste operazioni in modo consistente

Test Fixture: classe figlia di ::testing::Test;

Contiene oggetti che si vuole usare per composizione

Implementare metodo SetUp per definire cosa fare ad inizio test

Implementare metodo TearDown per definire cosa fare a fine test

(27)

gtest e classi: esercizi

Esercizio 1: testare la classe Integer attraverso gtest (lo facciamo insieme)

Esercizio 2: testare la classe Integer attraverso gtest con Fixture (lo facciamo insieme)

Esercizio 3: testare una classe Lista

Esercizio 4: testa la classe Matrix

(28)

Debugging

OK, avete trovato un bug, ed adesso?

Se i test sono ben congeniati, spesso accade attraverso un test

Talvolta la sorgente del bug è evidente

Talvolta è necessario debugging del codice

Analisi dettagliata del funzionamento del codice

Trovare la sorgente degli errori in generale (in C++)

in particolare, può essere difficile

(29)

Debugging

I bug sono di vario tipo

INCRT: il codice non genera uscita desiderata per alcuni input

Analisi del flusso del codice con print di debug

Analisi del flusso del codice con debugger

MEMFLT: il codice genera un fault di memoria

Necessario trovare l'errore con debugger

Utile usare metodi di analisi degli accessi alla memoria

NONDET: il codice non ha uscita deterministica

Spesso causati da utiizzo di memoria non inizializzata (simili al tipo 2)

Utile usare metodi di analisi degli accessi alla memoria

(30)

Debugging e print dello stato

Il primo e sempre valido metodo per il debugging di bug INCRT

Spesso metodo semplice è il più efficace

Consiglio di usare il preprocessore, esempio:

#if DEBUG > 1

cerr << “Variabile pippo:” << pippo;

#endif

Settare la variabile con opzione -DDEBUG NUM

(31)

Debugging con gdb

GDB: GNU debugger, debugger open-source per sistemi UNIX

Metodo più evoluto per il debugging di bug INCRT

ATTENZIONE: compilare con l'opzione -g del g++

perché il debugging sia leggibile

Permette di eseguire un programma passo-passo

Mentre si monitora lo stato di qualsiasi variabile

Possibile settare breakpoints

breakpoint: punto del codice in cui l'esecuzione deve fermarsi per poi reiniziarla

(32)

Debugging con gdb

Eseguire un programma con gdb

gdb nome_binario

Esce prompt dei comandi (gdb)

Settaggio della linea di comando

(gdb) set args opzioni_da_linea_di_comando

Settaggio breakpoint

(gdb) break nome_file.cc:numero_linea

Esempio

(gdb) break operator_streams.cc:10

(33)

Debugging con gdb

Esecuzione (si ferma a breakpoint)

(gdb) run

Se ridigitato si reinizia l'esecuzione dall'inizio

Esecuzione fino a punto specificato

(gdb) until nome_file.cc:numero_linea

Esempio

(gdb) until operator_streams.cc:20

(34)

Debugging con gdb

Esecuzione passo-passo (esegue 1 riga di codice)

Esegue prossima riga nel flusso del programma

Non necessariamente la riga successiva sul file.cc

Se chiamata funzione, entra in funzione (gdb) step

Stampa dello stack (per vedere catena chiamata funzioni nel punto attuale)

(gdb) backtrace

Possibile avanzare in alto o basso sullo stack (gdb) up (gdb) down

(35)

Debugging con gdb

Print di una variabile

(gdb) print nome_variabile

Esempio print el

Display di una variabile

Display è un print permanente

La variabile è stampata sullo schermo ogni volta che l'esecuzione si arresta

(gdb) display nome_variabile

Per uscire digitare quit o cntr-D

(36)

Debugging di errori di memoria

Errore provoca un segmentation fault, MEMFLT

In genere provocato da una errata gestione della memoria

SOLUZIONE 1

Compilare il programma con opzione -g

Rieseguire il programma per generare il core file (settando ulimit -c unlimited)

Analizzare il core file con un debugger come gdb

(37)

Debugging e core files

Core file è un dump dello stato della memoria usata da un binario

Nome da quando memoria era un core magnetico

Generabile in qualsiasi momento con una chiamata di sistema nei sistemi UNIX

Tipicamente viene generato in caso di un fault (di memoria o altro tipo)

Per abilitare la generazione ulimit -c unlimited

Se il binario usa molta memoria file può essere grande

Dal core file possibile a posteriori analizzare il fault

(38)

Core files e gdb

gdb permette di analizzare il core file

gdb nome_binario core_file

Una volta aperto il gdb permette di analizzare la memoria. Esempio per vedere il punto in cui è avvenuto l'errore

(gdb) backtrace

O andare up nello stack e verificare valore di variabili (gdb) up

(gdb) print el

(39)

Debugging e Valgrind

Valgrind è un tool open-source per l'analisi del software

Scaricabile liberamente da http://valgrind.org

O tramite pacchetto della distribuzione Linux usata

Nato come tool per fare check della memoria

(40)

Debugging e Valgrind

Valgrind è nato come tool per fare check della memoria

Oggi fa molto di più (vedremo cosa sono queste cose)

CPU e mem profiling, Cache profiler

Race condition in codice Multi-threaded

Valgrind usa una macchina virtuale con compilazione al volo (just-in-time)

Codice da eseguire viene tradotto in un linguaggio intermedio, poi riconvertito in codice da eseguire

Codice tradotto è tracciabile e monitorabile

(41)

Debugging e Valgrind

Valgrind è supportato da Linux e Mac OS X

Prezzo da pagare per la traduzione:

Codice gira da 5 a 20 volte più lento che il binario originale

Uso della memoria aumenta di molto

(42)

Debugging e Valgrind

Valgrind è il miglior metodo per analizzare i bug NONDET

Trova errori dovuti ad uso di memoria non inizializzata

Mappa tutte le celle di memoria usate come inizializzate o no

Genera errore se si accede a memoria non inizializzata

Usa tanta memoria e CPU aggiuntiva per fare questo

Trova inoltre bug dovuti a

deallocazione di memoria non allocata

Accesso out-of-boundary in vettori od a memoria non allocata in generale

(43)

Debugging e Valgrind

Vantaggio fondamentale nell'uso dei memcheck

Debuggers trovano errore quando avviene il fault

Valgrind trova l'errore appena avviene, esempio1:

int* v = new int[7];

for (i=0; i<15;++i) v[i] = 1; // ma v=new int[7];

Il fault avviene appena si esce dal boundary

Sia GDB che Valgrind lo tracciano

Memoria non allocata

scorro vettore Out-of-boundary, Segmentation Fault!

(44)

Debugging e Valgrind

Caso 2

int* v = new int[7]; int* w = new int[7];

for (i=0; i<15;++i) v[i] = 1;

Compilatore ha messo il vettore v e w accanto!

Non garantito ma probabile, succeda. Per ottimizzare

caching, SO alloca vicino aree di memoria del processo!

Segmantation Fault non avviene finché non si esce anche

Memoria non allocata

scorro vettore Non vado

out-of-boundary

Solo qui out-of-boundary

(45)

Debugging e Valgrind

Il fault non è nemmeno detto avvenga (ad esempio se il loop si ferma a 10)

Ma il programma era sbagliato!

In generale tracciano il Fault con GDB si trova il primo fault ,ma la sorgente iniziale dell'errore!

Valgrind traccia esattamente le allocazioni:

v → [v, v+7*sizeof(int)]

Ad ogni accesso in memoria v[i], controlla se si cade nel range [v, v+7*sizeof(int)]

Se non succede da un warning

(46)

Valgrind: memcheck, uso

Valgrind usa convertitore, pertanto non serve modificare la compilazione

Tranne usare l'ozione -g del g++ perché Valgrind dia messaggi più informativi

Uso

valgrind –tool=memcheck nome_binario argomenti

(47)

Valgrind: memory leaks

Valgrind può tenere traccia della memoria allocata e non più raggiungibile e mai deallocata

Traccia tutte le allocazioni fatte e verifica se la

memoria allocata è accessibile tramite un puntatore

Da messaggio di errore se non succede e conta la quantità di memoria persa

Uso

valgrind –tool=memcheck –leak-check=yes nome_binario args

(48)

Valgrind: controllo uso cache

Programmi che usano le cache di basso livello sono più veloci

Usare una struttura dati od un'altra possono cambiare il tasso di cache hit

Ma come analizzare tutto questo? In generale è nascosto al programmatore

Valgrind fornisce uno strumento per controllare il numero di accessi alle cache di diverso livello

Uso

valgrind –tool=cachegrind nome_binario args

(49)

CPU e mem profilers

Talvolta il software non presenta bags ma è troppo lento od usa troppa memoria

Come ottimizzare il consumo di memoria e CPU

Intanto, REGOLA 1 dell'ottimizzazione del codice

Non ottimizzare il codice presto

Inizialmente cura il design e la flessibilità

Ad esempio, non rinunciare mai a fare un metodo virtual

Ultimo passo: ottimizza DOVE SERVE

Il CPU o MEM profiler ti dicono dove val la pena di farlo

(50)

CPU profiler: funzionamento

Funzionamento basato su sampling

Ogni x millisecondi, si chiede al binario di fornire il suo stack

Stack fornisce la funzione in cui ci si trova, da chi si è chiamati, ecc.

La percentuale di volte in cui il sampling ha trovato che ci si trova in una funzione approssima la CPU usata dalla stessa

Possibile anche contare il numero di volte che si segue un path rispetto ad un altro

(51)

MEM profiler: funzionamento

Funzionamento basato su ridefinizione della libreria che gestisce le allocazioni di memoria

Non si chiama new, delete di sistema ma quelle definite in libreria aggiunta in linking

Le librerie aggiunte per il profiling in genere

Tracciano la funzione che chiama l'allocatore o deallocatore

Passano la chiamata all'allocatore o deallocatore di sistema

Si paga una penalità in performance, i binari su cui si fa il profiling sono più lenti

Aggiungere le librerie solo quando si ottimizza il codice

(52)

gprof

Strumento per effettuare cpu profiling integrato con g++, con utilizzo molto semplice

In compilazione e linking usare le opzioni -g e -pg

Eseguire il programma → genera file gmon.out

Per analizzare il file di output

gprof nome_programma gmon.out

Stampa profilo testuale

(53)

gprof

Profile testuale ottenuto ha formato

% cumulative self self total

time seconds seconds calls ms/call ms/call name 33.34 0.02 0.02 7208 0.00 0.00 open

16.67 0.03 0.01 244 0.04 0.12 offtime 16.67 0.04 0.01 8 1.25 1.25 memccpy 16.67 0.05 0.01 7 1.43 1.43 write

16.67 0.06 0.01 236 0.00 0.00 tzset 0.00 0.06 0.00 192 0.00 0.00 tolower 0.00 0.06 0.00 47 0.00 0.00 strlen 0.00 0.06 0.00 45 0.00 0.00 strchr 0.00 0.06 0.00 1 0.00 50.00 main 0.00 0.06 0.00 1 0.00 0.00 memcpy 0.00 0.06 0.00 1 0.00 10.11 print

0.00 0.06 0.00 1 0.00 0.00 profil 0.00 0.06 0.00 1 0.00 50.00 report

(54)

google-perftools: introduzione

Strumento per effettuare cpu e mem profiling

Liberamente scaricabile (utilizzabile senza restrizioni) da

http://code.google.com/p/google-perftools/

Istallazione ./configure make

make install

(55)

google-perftools: utilizzo CPU

In Linking

aggiungi -lprofiler

In esecuzione

CPUPROFILE=/tmp/mybin.prof binario_con_cprofiler_linkato

Per analizzare l'output

pprof --text binario /tmp/mybin.prof

o in modalità grafica (richiede dot e ghostview):

pprof --gv binario /tmp/mybin.prof

(56)

google-perftools: utilizzo MEM

In Linking

aggiungi -ltcmalloc

In esecuzione

HEAPPROFILE=/tmp/mybin.prof binario_con_mprofiler_linkato

Per analizzare l'output

pprof --text binario /tmp/mybin.prof

o in modalità grafica (richiede dot e ghostview):

(57)

google-perftools: output

Modalità testuale, collezione di linee

14 2.1% 17.2% 58 8.7% std::_Rb_tree::find ...

numero samples la funzione era ultima sullo stack

% samples in cui si era nella funzione (% volte la funzione era ultima sullo stack)

% of profiling samples in the functions printed so far

numero samples la funzione era sullo stack

% di profiling samples nella funzione e nelle funzioni chiamate (% volte che la funzione era sullo stack)

Nome della funzione

(58)

google-perftools: output

Modalità grafica

(59)

Valgrind: mem profiling

Anche Valgrind ha strumento per l'analisi dell'uso della memoria

Controlla periodicamente l'uso della memoria

Possibile tracciare come aumenta nel tempo e chi la richiede

Uso

valgrind –tool=massif nome_binario args

Riferimenti

Documenti correlati

TEST Questo è possible, il rimedio consiste nel ripianificare le attività o nel cambiare il tipo di posizione e di duratat temporale, nell’ambito delle possibilità del progetto

WP1. Sistema di caratterizzazione, di monitoraggio e di miglioramento delle funzionalità di matrici ambientali ed industriali. In particolare, il focus sarà sull’uso di metodi

WP1. Sistema di caratterizzazione, di monitoraggio e di miglioramento delle funzionalità di matrici ambientali ed industriali. In particolare, il focus sarà sull’uso

[r]

ESERCIZIO svolto in classe il 25 novembre 2013. Abbiamo ripreso l’esercizio svolto il

Il punto P rappresenta il luogo (distante 59m dal punto di partenza del cane più lento) e l’istante in cui i due cani si incontrano.. Questo file può essere

Dal riquadro di figura leggiamo il valore della corrente continua di polarizzazione diretta (Continuous Forward Current): 20mA.. Questo è il valore della corrente

Osserviamo che il generatore ed il resistore sono in serie e, quindi, sono attraversati dalla stessa corrente... Il valore commerciale più vicino