Astrazione sui dati, ereditarietà, polimorfismo, binding dinamico, genericità, impattano concetti ed attività del testing.
Necessità di nuovi livelli di test:
Necessità di una nuova infrastruttura:
I livelli di Test tradizionali mal si adattano al caso di linguaggi OO.
Da cosa è rappresentata l’unità?
Cosa è un modulo in un sistema OO?
Una possibile suddivisione:
Componente base: Classe = struttura dati + insieme di operazioni
La ‘opacità’ dello stato (v. incapsulamento ed information hiding) rende più difficile la costruzione di infrastruttura e oracoli
Test ed Ereditarietà
Ci si può “fidare” delle proprietà ereditate?
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:
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?
Anche in un caso così semplice, singola invocazione di un metodo, si hanno 27 possibili combinazioni (senza tener conto dello stato dell'oggetto)!
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:
Problema principale: non-determinismo
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).
Berard nel 93[1] propose il seguente approccio al testing OO:
- 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.
Fault-based testing
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.
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.
Scenario-Based Test Design
1. Identificazione delle variabili operazionali: input ed output espliciti, condizioni di ambiente, stati del sistema, elementi di interfaccia.
2. Definizione del dominio (e delle classi di equivalenza) delle variabili
3. Sviluppo delle relazioni fra input e output, modellate mediante tabelle di decisione.
4. Sviluppo dei casi di test per ciascun variante:
Testing della Classe e della Gerarchia di classi
La completa copertura di una classe richiede di:
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.
Il testing white box di una classe deve tenere in conto tutti i possibili stati e cambiamenti di stato degli oggetti di quella classe
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).
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
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:
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.
Occorre considerare anche i possibili comportamenti alternativi (es. nome corso scorretto, corso non disponibile, corso inesistente, …)
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:
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).
2. Ciclo di Vita e Processi Software
3. Processi per lo sviluppo rapido del software
4. Sviluppo Agile del Software
5. Test Driven Development (TDD)
7. Component Based Software Engineering (CBSE): Generalità
8. Component Based Software Engineering (CBSE): Il processo di svi...
9. Ingegneria del Software orientato ai Servizi
10. Ingegnerizzazione dei Servizi
11. I Processi di Manutenzione del Software
12. Reengineering, Refactoring e Reverse Engineering del Software
13. Verifica e Convalida del Software. Richiami e concetti di base ...
14. Tecniche di Testing Dinamico
15. Testing di Sistemi Object Oriented
16. Automazione del testing e Analisi Mutazionale
17. Tecniche di Analisi Statica del codice e il Debugging
18. Stima dei costi nei progetti Software
19. Il Modello COCOMO per la stima dei costi Software – La gestio...
20. Gestione e Miglioramento dei Processi di Produzione del Softwar...
21. La Valutazione della Qualità dei Processi Software – Il Capa...