Vai alla Home Page About me Courseware Federica Living Library Federica Federica Podstudio Virtual Campus 3D La Corte in Rete
 
Il Corso Le lezioni del Corso La Cattedra
 
Materiali di approfondimento Risorse Web Il Podcast di questa lezione

Anna Rita Fasolino » 15.Testing di Sistemi Object Oriented


Impatto delle caratteristiche del paradigma Object Oriented sul Testing

Astrazione sui dati, ereditarietà, polimorfismo, binding dinamico, genericità, impattano concetti ed attività del testing.

Necessità di nuovi livelli di test:

  • il concetto di classe, come stato + operazioni, cambia il concetto di unità;
  • le modalità di interazione ed integrazione degli oggetti impatta il test di integrazione.

Necessità di una nuova infrastruttura:

  • driver e stub devono considerare lo stato (information hiding);
  • lo stato non può essere ispezionato con tecniche tradizionali.

Impatto delle caratteristiche del paradigma Object Oriented sul Testing (segue)

  • Necessità di nuove tecniche di generazione di casi di test e criteri di terminazione del testing che tengano conto di:
  • Stato degli oggetti
  • Polimorfismo e binding dinamico
  • Ereditarietà
  • Genericità
  • Eccezioni

Nuovi Livelli di Test

I livelli di Test tradizionali mal si adattano al caso di linguaggi OO.

Da cosa è rappresentata l’unità?

  • un oggetto? una classe? un’operazione di una classe?

Cosa è un modulo in un sistema OO?

  • Esistono diverse scuole di pensiero.

Una possibile suddivisione:

  • Basic unit testing: test di una singola operazione di una classe;
  • Unit testing: test di una classe nella sua globalità;
  • Integration testing: test delle interazioni tra più classi.

Stato ed Information Hiding

Componente base: Classe = struttura dati + insieme di operazioni

  • oggetti sono istanze di classi
  • la verifica del risultato del test non è legata solo all’output, ma anche allo stato,definito dalla struttura dati

La ‘opacità’ dello stato (v. incapsulamento ed information hiding) rende più difficile la costruzione di infrastruttura e oracoli

  • è sufficiente osservare le relazioni tra input e output?
  • lo stato di un oggetto può essere inaccessibile;
  • lo stato “privato” può essere osservato solo utilizzando metodi pubblici della classe (e quindi affidandosi a codice sotto test).

Nuove Tecniche di generazione dei Test

Test ed Ereditarietà

  • l’ereditarietà è una relazione fondamentale tra classi
  • nelle relazioni di ereditarietà alcune operazioni restano invariate nella sotto-classe, altre sono ridefinite, altre aggiunte (o eliminate)

Ci si può “fidare” delle proprietà ereditate?

  • È necessario identificare le proprietà che occorre ri-testare: operazioni aggiunte, operazioni ridefinite, operazioni invariate ma influenzate dal nuovo contesto
  • Può essere necessario verificare la compatibilità di comportamento tra metodi omonimi in relazione classe-sottoclasse
  • Riuso di test, e necessità di test specifici
  • Ereditarietà produce nuova forma di Integrazione/Interazione

Test e Genericità

  • I moduli generici sono presenti nella maggior parte dei linguaggi OO: la genericità è un concetto chiave per la costruzione di librerie di componenti ri-usabili.
  • Le classi parametriche devono essere instanziate per poter essere testate: bisognerebbe prevedere test per ogni possibile istanziazione della classe parametrica (test esaustivo???)
  • Quali ipotesi fare sui parametri? Servono classi “fidate” da utilizzare come parametri
  • Quale metodologia seguire quando si testa un componente generico che è ri-usato? Non esistono tecniche o approcci maturi in letteratura

Polimorfismo e Binding Dinamico

Un riferimento (variabile) può denotare oggetti appartenenti a diverse classi di una gerarchia di ereditarietà (polimorfismo), ovvero il tipo dinamico e il tipo statico dell’oggetto possono essere differenti:

  • più implementazioni di una stessa operazione;
  • il codice effettivamente eseguito è identificato a run-time, in base alla classe di appartenenza dell’oggetto (binding dinamico).

Polimorfismo e problemi per il testing

Il test strutturale può diventare non praticabile.

Come definire la copertura in un’invocazione su un oggetto polimorfico?

Come creare test per “coprire” tutte le possibili chiamate di un metodo in presenza di binding dinamico?

Come gestire i parametri polimorfici?

Polimorfismo e Binding Dinamico: esempio


Esempio

Anche in un caso così semplice, singola invocazione di un metodo, si hanno 27 possibili combinazioni (senza tener conto dello stato dell’oggetto)!

Anche in un caso così semplice, singola invocazione di un metodo, si hanno 27 possibili combinazioni (senza tener conto dello stato dell'oggetto)!


Altri Problemi: gestione delle eccezioni

Le eccezioni modificano il flusso di controllo senza la presenza di un esplicito costrutto di tipo test and branch, rendendo inefficace il Control-Flow-Graph per modellare il flusso di controllo.

E’ necessario introdurre ulteriori metodi per generare casi di test e ulteriori metriche di copertura per valutare l’effettiva copertura di tutte le eccezioni:

  • copertura ottimale: sollevare tutte le possibili eccezioni in tutti i punti del codice in cui è possibile farlo (può non essere praticabile);
  • copertura minima: sollevare almeno una volta ogni eccezione.

Altri Problemi: Concorrenza

Problema principale: non-determinismo

  • risultati non-deterministici;
  • esecuzione non-deterministica.

Casi di test composti solo da valori di Input/Output sono poco significativi.

Casi di test devono essere composti da valori di Input/output e da una sequenza di eventi di sincronizzazione (occorre però forzare lo scheduler a seguire una data sequenza).

Approcci per il Testing OO

Berard nel 93[1] propose il seguente approccio al testing OO:

  1. Associare ogni test alla classe da testare
  2. Specificare lo scopo del Test
  3. Specificare una sequenza di passi di test, contenente:
  • elenco di stati per l’oggetto da testare;
  • elenco di messaggi ed operazioni che saranno eseguite come conseguenza del test;
  • elenco di eccezioni che potrebbero verificarsi durante il test;
  • elenco di condizioni esterne (dell’ambiente esterno) che devono sussistere per eseguire il test;
  • ulteriori informazioni per capire ed implementare il test.

[1]: E. Berard, Essays on Object-Oriented Software Engineering, vol.1, Addison Wesley, 1993.

Alcuni metodi per il testing OO

    Metodi per il Testing OO

    Fault-based testing

    • Il tester si fa guidare dai possibili difetti presenti nel codice, e progetta i casi di test cercando di metterli in evidenza. Necessità di una classificazione (tassonomia) dei difetti ricorrenti.
    • Es. esistono varie categorie di difetti proposte in letteratura relativi ai metodi, alle variabili di istanza, ai messaggi, alla classe, all’ereditarietà, all’astrazione della classe, etc..
    • Spesso tali classificazioni si costruiscono a partire dai bug report di applicazioni esistenti.

    Tipici Difetti in codice OO

    Interazioni scorrette fra metodi (individualmente corretti) di superclassi e subclassi.

    Mancata nuova specifica di un metodo ereditato (overriding) in una subclasse, in una profonda gerarchia di ereditarietà.

    La subclasse eredita metodi non appropriati dalle superclassi.

    Fallimenti in chiamate polimorfiche di una subclasse la cui interfaccia è conforme alle interfacce delle superclassi.

    Mancata o scorretta inizializzazione della superclasse nelle subclassi.

    Scorretto aggiornamento di variabili di istanza di una superclasse all’interno delle subclassi.

    Tipici Difetti in codice OO (segue)

    Polimorfismo ‘a spaghetti’ che produce perdita del controllo (il problema dello “yo-yo” causato dal dynamic binding e oggetti self e super).

    Subclassi violano il modello di stato o l’invariante della superclasse.

    Istanziazioni di classi generiche con un tipo di parametro non testato.

    Relazioni di controllo inter-modulo scorrette, a causa della delocalizzazione di funzionalità in classi piccole e incapsulate.

    Metodi per il Testing OO

    Scenario-Based Test Design

    • È un testing in cui ci si concentra su ciò che fa l’utente, piuttosto che su ciò che fa il software.
    • Richiede che vengano catturati i task eseguibili dall’utente (es. i casi d’uso), per poi usarli insieme ai loro scenari alternativi come casi di test.
    • In pratica, si andranno a coprire con i casi di test tutte le varianti di comportamento di un caso d’uso.

    Esempi di Casi d’uso e scenari


    Dai casi d’uso ai casi di test

    1. Identificazione delle variabili operazionali: input ed output espliciti, condizioni di ambiente, stati del sistema, elementi di interfaccia.

    • Es.: Inizia Sessione: PIN della tessera, PIN inserito, risposta dalla banca, stato del conto del cliente.

    2. Definizione del dominio (e delle classi di equivalenza) delle variabili

    • Es.: Inizia Sessione: PIN ∈ [0000- 9999].

    3. Sviluppo delle relazioni fra input e output, modellate mediante tabelle di decisione.

    4. Sviluppo dei casi di test per ciascun variante:

    • Un caso di test che renda vero ciascun variante;
    • Almeno un caso di test che renda falso ciascun variante;
    • In genere un caso di test “vero” per un variante coincide con uno “falso” per un altro variante.

    Sviluppo della tabella di decisione per il caso d’uso “Inizia Sessione”


    Progetto dei Casi di Test


    Metodi per il Testing OO

    Testing della Classe e della Gerarchia di classi

    La completa copertura di una classe richiede di:

    • testare tutte le operazioni di un oggetto;
    • settare ed interrogare tutti gli attributi di un oggetto;
    • esercitare l’oggetto in tutti i possibili stati.

    L’ereditarietà rende più difficile la scelta dei casi di test degli oggetti giacchè le informazioni da testare non sono localizzate (ma distribuite nella gerarchia di ereditarietà).

    L’ereditarietà non permette di risparmiare sul testing delle classi derivate che, invece, dovranno essere ritestate comunque.

    Testing degli stati di una classe di oggetti

    Il testing white box di una classe deve tenere in conto tutti i possibili stati e cambiamenti di stato degli oggetti di quella classe

    • uso di State Chart per rappresentare tale evoluzione;
    • uso di vari Criteri di copertura;
    • copertura degli Stati, delle Transizioni, …
    • copertura dei cammini tra stato iniziale e stato finale di un oggetto.

    Non basta valutare solo gli output restituiti dai metodi ma anche lo stato dell’oggetto dopo la chiamata … ma alcuni attributi potrebbero essere privati o protetti (e dunque non osservabili).

    Es.: interfaccia dell’oggetto Weather station (Stazione Meteo)


    State diagram della Stazione Meteo


    Il testing della Stazione Meteo

    Occorre prima definire test cases per tutti I singoli metodi: reportMeteo, calibra, test, startup e shutdown.

    Usando un modello a stati, bisogna identificare le transizioni di stato da testare e le sequenze di eventi che causano tali transizioni.

    Ad esempio, una possibile sequenza di stati da esercitare può essere:

    Attesa-> Calibratura -> Testing -> Trasmissione -> Attesa

    Testing di Integrazione – Testing Inter-classi

    Gli oggetti collaborano nella realizzazione di funzionalità complesse. Occorre testare tali collaborazioni.

    La generazione dei casi di test può essere effettuata a partire dai diagrammi di interazione UML (tratti dalle specifiche)

    Opportuno costruire diagrammi di interazione anche dal codice e verificare la corrispondenza con le specifiche …

    Problemi:

    • ereditarietà;
    • polimorfismo e binding dinamico.

    Generazione di Casi di Test a partire dai diagrammi di Interazione

    I diagrammi di interazione (quali i sequence diagram UML) indicano possibili sequenze di messaggi.

    Dovrebbero indicare i casi frequenti e quelli particolari.

    Selezione immediata: generare un test per ogni diagramma di interazione.

    Selezione più accurata: per ogni diagramma individuare possibili alternative e per ogni alternativa selezionare un ulteriore insieme di casi di test.

    Esempio

    Occorre considerare anche i possibili comportamenti alternativi (es. nome corso scorretto, corso non disponibile, corso inesistente, …)

    Occorre considerare anche i possibili comportamenti alternativi (es. nome corso scorretto, corso non disponibile, corso inesistente, …)


    Problemi per l’automazione dell’OOT

    Scarsa controllabilità, osservabilità e testabilità del sistema, a causa del numero elevato di piccoli oggetti incapsulati.

    Difficoltà nell’analizzare la copertura white-box, a causa di un elevato numero di complesse dipendenze dinamiche.

    L’allocazione dinamica di oggetti crea delle dipendenze tra classi a tempo di esecuzione:

    • diventa difficile capire quanti e quali stub dover creare per poter testare una classe indipendentemente dalle altre;
    • diventa difficile capire in che ordine svolgere il testing di integrazione bottom-up.

    Problemi per l’automazione dell’OOT (segue)

    Difficoltà nel produrre drivers, stubs, e test suites che tengano conto della struttura di ereditarietà del sistema.

    Possibilità di riusare i test case di superclassi nel (regression) testing di subclasses.

    Spesso il codice dell’applicazione non è completamente disponibile (se si usano ad esempio object-oriented frameworks).

  • Fault-based testing
  • Scenario-based testing
  • Testing della classe
    • Contenuti protetti da Creative Commons
    • Feed RSS
    • Condividi su FriendFeed
    • Condividi su Facebook
    • Segnala su Twitter
    • Condividi su LinkedIn
    Progetto "Campus Virtuale" dell'Università degli Studi di Napoli Federico II, realizzato con il cofinanziamento dell'Unione europea. Asse V - Società dell'informazione - Obiettivo Operativo 5.1 e-Government ed e-Inclusion

    Fatal error: Call to undefined function federicaDebug() in /usr/local/apache/htdocs/html/footer.php on line 93