Collaudo del Software

Alberto Ferrari - uniPr

Testing

  • Il collaudo del software (testing) è un procedimento utilizzato per individuare problemi di correttezza, completezza e affidabilità del software
  • Consiste nel valutare se il software rispetta i requisiti (specifiche) precedentemente definiti
  • Non confondere il testing con
    • il debugging (individuazione dei bug)
    • il profiling (analisi complessità computazionale)
    • il benchmarking (misura delle prestazioni)

Specifiche

  • Rispetto a cosa valutiamo correttezza o affidabilità di un programma?
  • Idea del programmatore
    • Non formulata, non documentata
    • Incompleta, mutevole, facilmente dimenticata
  • Specifiche (formali o informali)
    • Formulate, scritte, studiate e condivise
      → Parte del progetto e del programma
    • Spec. assiomatiche: espressioni logiche o asserzioni
      Precondizioni, postcondizioni e invarianti

Costo dei bug

  • Scovare bug non è un compito facile
    • Costoso: non è insolito dedicare al testing il 40% del tempo e delle risorse di un progetto
  • Far emergere bug in prime fasi dello sviluppo!
    • B. Boehm: se trovare e correggere un problema in fase di specifica dei requisiti costa 1$...
    • 5$ in progetto, $10 in programmazione,
    • $20 in unit testing, fino a $200 dopo consegna
    • Bug possibili già a causa di specifiche non ben chiare e capite

Costo dei bug

Ariane 5

  • Il primo volo dell'Ariane 5 fallì e il razzo si autodistrusse per un malfunzionamento del software di controllo
  • Un dato a 64 bit in virgola mobile venne convertito in un intero a 16 bit con segno, questa operazione causò una trap del processore
  • Per motivi di efficienza i progettisti avevano disabilitato il controllo software sulle trap
  • Fu necessario quasi un anno e mezzo per capire quale fosse stato il malfunzionamento e un danno stimato di 500.000.000$
  • Video

Costo dei bug

Mars Polar Lander

  • Il Mars Polar Lander fa parte di una coppia di sonde del Programma Mars Surveyor, assieme al Mars Climate Orbiter. La missione delle due sonde era di studiare la meteorologia, il clima e le quantità di acqua e di anidride carbonica del pianeta Marte.
  • Le comunicazioni con il Mars Polar Lander si persero prima del suo ingresso nell'atmosfera marziana.
  • La teoria piu' accreditata e' che un errore di software (una variabile non inizializzata) abbia fatto spegnere i motori ad una quarantina di metri di quota, per cui la sonda avrebbe impattato il suolo ad un centinaio di km/h.
  • Le immagini sembrano confermare la teoria, mostrando il terreno contaminato dagli scarichi dei motori

Costo dei bug

... altri disastri software ...

  • 1983, sfiorato il conflitto mondiale
    • Il sistema di allarme sovietico invia informazioni sbagliate e si sfiora la 3 guerra mondiale.
    • Colpa di un bug nel software del sistema di allarme che rileva cinque missili balistici in arrivo dagli Stati Uniti.
  • 1990, il crollo della rete At&T
    • Un piccolo problema meccanico in una stazione della compagnia telefonica provoca la "caduta" di 75 milioni di telefonate.
    • Colpa di una linea di codice difettosa aggiunta durante un processo di upgrade del software.
    • L'American Airlines per colpa di questo bug ci rimette 200mila prenotazioni.

Verifica e validazione

Verifica e validazione

  • Mostrare che il sistema...
    • È conforme alle specifiche
    • Soddisfa i bisogni dell’utente
  • Comprende revisione e collaudo del sistema
  • Test case, derivati dalle specifiche

Prove formali

  • Dimostrazione matematica di un programma: alternativa (~ accademica) al testing
    • Annotazione del programma con asserzioni matematiche: comportamento atteso
    • Proprietà valide per i vari costrutti del programma
  • Prova che post-condizioni verificate, se:
    • Precondizioni verificate
    • Programma termina
  • Dimostrazioni automatiche
    • Se a mano → errori (più che nel programma?)

Revisione del software

  • Analisi del codice (o pseudocodice) per capirne le caratteristiche e le funzionalità
  • Code walk-through
    • Selezione porzioni di codice e valori di input
    • Simulazione su carta comportamento del sistema
  • Code inspection, più formale e focalizzato
    • Uso di variabili non inizializzate
    • Loop infiniti
    • Letture di porzioni di memoria non allocata
    • Rilascio improprio della memoria

Testing

Testing

  • Il test di un programma prevede l’esecuzione di una o più serie di test-case
  • Struttura di un test-case
    • le precondizioni per la corretta esecuzione del caso
    • le azioni per l’esecuzione del caso
    • i risultati attesi dall’esecuzione e i criteri per dichiarare l’esito positivo o negativo

Le operazioni di testing possono individuare la presenza di errori nel software ma non ne possono dimostrare la correttezza. (E. Dijkstra)

Eseguire un programma con l'intento di trovare errori. (Glen Myers, “The art of Software Testing”)

  • Verificare sistema in un insieme abbastanza ampio di casi... → plausibile comportamento analogo anche nelle restanti situazioni

Testers Vs Programmers

  • Il programmatore e il tester hanno obiettivi contrapposti
  • In alcuni casi è preferibile che sia persone diverse

Classificazione dei test

  • Tipi di test
    • White box (in the small)
    • Black box (in the large)
  • Livelli di test
    • Unit test (test dei singoli componenti)
    • Integration test (test globale dopo unit test)
    • System test
  • Ripetizione di test
    • Regression test

Testing

  • Black box testing – a scatola chiusa
    • si controllano solo i requisiti di funzionamento
  • White box testing – a scatola aperta
    • si cerca di verificare ogni riga del codice

White-box testing

  • Test basati sulla conoscenza della struttura interna del codice
  • Un errore non può essere scoperto se la parte di codice che lo contiene non viene mai eseguita

White-box testing

  • Statement test
    • Insieme di test T tali che, eseguendo su tutti i casi di T il programma P, ogni istruzione di P venga eseguita almeno una volta
      • Normalmente viene testato il 60% dei possibili path interni a un software
      • Col supporto di test automatizzati si può arrivare all'85-90%
      • Il 100% è utopia ...
    • Branch test (copertura delle decisioni)
    • Branch & condition test (… condizioni)

Basic path testing

  • Scelto insieme minimo di percorsi per coprire tutte le istruzioni e condizioni (white box)
    • Tracciare diagramma di flusso
    • Astrarre il diagramma in un grafo di flusso
    • Complessità ciclomatica n = metrica di test
    • Trovare n casi di test che seguono ciascun cammino indipendente
  • Cammino: sequenza di comandi, da inizio a fine
  • Cammino indipendente: aggiunge almeno una nuova istruzione rispetto ai cammini già identificati

Diagramma di flusso

def f():
    // entry
    while a:
        x()
        if b:
            if c: y()
            else z()
            # p
        else:
             v()
             w()
        # q
    # exit: r

Grafo di flusso

  • Piccola astrazione rispetto a diagramma di flusso
  • Complessità ciclomatica, dalla teoria dei grafi:
    • Numero di possibili cammini indipendenti, o...
    • Numero di regioni del grafo di flusso, o...
    • Numero di nodi predicato + 1
       
  • A, r
  • A, X, B, C, Y, p, q, A, r
  • A, X, B, C, Z, p, q, A, r
  • A, X, B, V, W, q, A, r

Black box testing

  • Sistema = scatola nera; si verificano le corrispondenze di input e output
    • White-box testing: impossibile per grandi sistemi
    • Test case scelti in base alle specifiche dei requisiti
  • Desiderata: trovare errori...
    • Funzionali: otteniamo i risultati attesi per dati input di un metodo?
    • Interfaccia: dati passati correttamente tra i metodi?
    • Efficienza: il metodo è abbastanza veloce?

I test

  • Pianificazione dei test
  • Progettazione dei test
  • Esecuzione dei test
  • Automatizzazione dei test

Partizioni d’equivalenza

  • Partizionamento ingressi in classi di equivalenza
    • Irrealistico testare tutti i possibili ingressi (es. sqrt)
    • Ipotesi: sufficiente testare un solo caso per classe
    • Si includono casi limite e valori non validi
    • Precondizioni: riducono il numero di casi di test
def swap_elements(v: list, i: int, j: int):
    '''
    Exchange element i and j in list v
    v: empty, one element, more elements
    i, j: one or both indexes out of range... or both in range: i < j, i > j, i = j
    '''
    # ...

Regression testing

  • Scopo: trovare errori di regressione
    • Errori in un programma che prima era corretto, ed è stato modificato di recente
    • Un errore di regressione è un errore che prima non c’era
  • Dopo la modifica di una parte P nel programma Q
    • Testare che la parte P funzioni correttamente
    • Testare che l’intero programma Q non sia stato danneggiato dalla modifica

Test-Driven Development

  • Le metodologie agile tendono a enfatizzare il ruolo dei test
  • Viene teorizzata la codifica dei test prima ancora dello sviluppo del codice dell’applicazione da realizzare
  • (TDD, Test-Driven Development)
    • Nella fase rossa si scrive un test automatico per la nuova funzione da sviluppare
      • il test fallisce in quanto la funzione non è stata ancora realizzata
    • Nella fase verde si scrive la quantità minima di codice necessaria per passare il test
    • Nella fase di refactoring si esegue il refactoring del codice per adeguarlo a determinati standard di qualità

Java - Testing con JUnit

  • I test JUnit non richiedono continuo intervento o giudizio da parte dell'utente
  • Facile eseguire molti test assieme, su un certo progetto
  • Come definire un test:
    • Annotare un metodo di test con @org.junit.Test
    • Per controllare la validità di una espressione, usare assertTrue(boolean)
    • Import statico: import org.junit.Assert.*
    • Non è richiesto di estendere nessuna classe

JUnit assertions

assertArrayEquals(...) // Asserts that two arrays are equal.
assertEquals(...) // Asserts that two objects are equal.
assertFalse(...) // Asserts that a condition is false.
assertNotNull(...) // Asserts that an object isn't null.
assertNotSame(...) // Asserts that two objects do not refer to the same object.
assertNull(...)
assertSame(...)
assertTrue(...) // Asserts that a condition is true.
fail(...) // Fails a test

Esempio di test

  • Controllare che una pallina rimbalzi correttamente contro il bordo inferiore
@Test
public void testBounceDown() {
    Ball b = new Ball(8, 11); // dx = 1, dy = 1, w = 16, h = 12
    b.move();
    assertTrue(b.getX() == 9 && b.getY() == 10);
}

C++ - Testing con CppUnit

  • CppUnit is the C++ port of the famous JUnit framework for unit testing.
  • Test output is in XML or text format for automatic testing and GUI based for supervised tests.
  • If assertion fails, an exception is thrown
  • MACRO CppUnit ... #include
  • CPPUNIT_ASSERT(condition):
  • CPPUNIT_ASSERT_MESSAGE(message,condition):
  • CPPUNIT_ASSERT_EQUAL(expected,current):
  • CPPUNIT_ASSERT_EQUAL_MESSAGE(message,expected,current):
  • CPPUNIT_ASSERT_DOUBLES_EQUAL(expected,current,delta):

Code refactoring

Code refactoring

  • Il code refactoring è una tecnica per modificare la struttura interna di porzioni di codice senza modificarne il comportamento esterno
  • L'obiettivo è migliorare alcune caratteristiche non funzionali del software:
    • leggibilità
    • manutenibilità
    • riusabilità
    • estendibilità
    • eliminazione di code smell
  • Molti ambienti di sviluppo offrono valide funzionalità di ausilio al refactoring

Refactoring is a disciplined technique for restructuring an existing body of code, altering its internal structure without changing its external behavior [Martin Fowler]

Consigli pratici

... anche per codice di "piccole" dimensioni ...

code smell e anti-pattern

  • Evitare code smell:
    • Identificatori non significativi (i,j,k ... )
    • Valori "cablati" nel codice (hard code)
    • Copy and paste programming (Don't Repeat Yourself!)
  • anti-pattern
    • Dead code (codice irraggiungibile)
    • Spaghetti code (flusso incomprensibile)
    • Kitchen sink (lavello della cucina) o Swiss army knife ("coltellino svizzero") classe che contiene un gran numero di operazioni complesse ed eterogenee tra loro
    • ...

Convenzioni di scrittura del codice

  • Indentation
  • Breaking long lines and strings
  • Placing Braces and Spaces
  • ...
  • Functions
  • Commenting

"This is a short document describing the preferred coding style for the linux kernel. Coding style is very personal, and I won't force my views on anybody, but this is what goes for anything that I have to be able to maintain, and I'd prefer it for most other things too. Please at least consider the points made here. First off, I'd suggest printing out a copy of the GNU coding standards, and NOT read it. Burn them, it's a great symbolic gesture [Linus Torvalds]

Linux Kernel Coding Style

Come collaudare il codice?

  • Usare un debugger per valutare espressioni in fase di esecuzione
    • Si può decidere cosa valutare a seconda del flusso di esecuzione e dei valori generati, senza ricompilare
  • Istruzioni di stampa all'interno del programma
    • Valore di espressioni scritto a console o su file di log
  • Entrambi gli stili, scarsamente automatizzati
    • Necessità di intervento attivo durante l'esecuzione dei test
    • Giudizio dei risultati da parte dell'utente
    • Quali valori analizzare? Sono coerenti?
  • Scarsamente componibili
    • Difficile controllare molte espressioni nel debugger
    • "Scroll blindness": troppe istruzioni di stampa ⇒ codice poco leggibile

Alberto Ferrari
Universita' degli Studi di Parma