2. Il collaudo del software
È la fase del ciclo di vita del software in cui vengono individuati problemi di
correttezza, completezza ed affidabilità della soluzione in via di sviluppo.
Si cercano di trovare gli input, gli stati o le piattaforme hardware con cui è possibile
sollecitare il software.
Questo processo non garantisce l’assenza di difetti, ma tende a ridurne la probabilità
ad un livello accettabile dall’utente/cliente. Probabilità che può variare, anche di
molto, in base alla destinazione d’uso del software.
9. Un piccolo esempio: Testiamo uno statement SQL
INSERT INTO Users VALUES (1, ‘Domenico’, ‘dometec@gmai.com’)
● Hai la connessione al database?
● Sei autenticato?
● Hai le autorizzazioni per scrivere sulla tabella?
● La tabella ha altri campi che devono essere valorizzati?
● C’è spazio sul disco?
● L’ID è duplicato?
● …
● ...
10. Testare tutto...
..non è possibile o quantomeno non è praticabile. Inoltre un
software testato non è scontato che soddisfi le esigenze del
cliente.
Allora cosa testare?
11. Il collaudo del software è importante:
● A lunga scadenza riduce i costi
● Rende il sistema più sicuro
● Aumenta la qualità del prodotto
● Aumenta la soddisfazione del cliente
15. 7 principi fondamentali del testing
3. Testare il prima possibile
https://cdn.softwaretestinghelp.com/wp-content/qa/uploads/2018/11/early-testing-defect-fixing-cost.png
16. 7 principi fondamentali del testing
4. Defect clustering (80%
dei problemi si verifica sul
20% dei moduli)
17. 7 principi fondamentali del testing
5. Pesticide paradox
(Aggiungere e tenere
aggiornati i test)
26. Testare ad ogni livello
Indipendentemente dal modello di sviluppo portato avanti è
importante testare a tutti i livelli, dalla raccolta dei requisiti alla
manutenzione.
27. Unit test
Anche detto Component Testing, verifica l’implementazione del singolo modulo in
maniera indipendente.
Gli unit test sono strettamenti legati al codice e per questo solitamente sono gli
sviluppatori ad implementarli.
28. Integration Testing
Questi test vengono svolti per capire se l’integrazione tra i moduli software è
corretta.
Automatizzati come per gli Unit Test, vengono spesso eseguiti da sistemi di
Continuous Integration come Jenkins.
Due modalità di implementazione:
● Big-Bang
● Incremental Testing (stub)
29. System Testing
Verifica end-to-end del software dal punto di vista dell’utente finale.
Vengono anche verificati i requisiti non funzionali (velocità del sistema, user
experience).
Spesso svolto da un team apposito usando procedure manuali.
30. Acceptance Testing
Sono test svolti dal cliente, più che per trovare difetti, per verificare che il software
prodotto sia aderente alle sue necessità.
Può essere realizzato in due modi:
● Alpha testing
● Beta testing
31. Non-Functional Testing
Il sistema viene testato su:
● Performance
● Scalabilità
● Recovery
● Sicurezza
● Documentazione
● Usabilità
● Recovery
32. Regression Testing
Assicurano la stabilità funzionale e non funzionale del sistema a seguito di una
modifica.
(Principio 5. Pesticide paradox)
40. TDD
● Non scrivere codice senza che esista un test automatico
che fallisce.
● Mantieni una TODO list aggiornata.
● Sei costretto a pensare prima di scrivere un test.
● Ed a chiedere aiuto, subito, quando non capisci una
funzionalità.
● Rifattorizzi spesso.
41. TDD
● Usi il debugger solo quando necessario, e nel punto giusto
(se i test sono sufficientemente piccoli).
● Descrivi il codice nel modo più rapido e aggiornato
possibile, indicando cosa vuoi ottenere.
● Meno commenti, meno documentazione.
● Libertà di rifattorizzare senza provare rimorsi.
● Basso accoppiamento fra gli oggetti e alta coesione
43. Basso accoppiamento ed alta coesione
Sono importati indici per indicare la qualità del codice.
Coesione: fa riferimento al numero e alla eterogeneità dei compiti di cui una singola
unità è responsabile (classe o metodo); Se ciascuna unità è responsabile di un singolo
compito, diciamo che tale unità ha una alta coesione.
Accoppiamento: L’accoppiamento si riferisce ai legami tra unità separate di un
programma; Se due classi dipendono strettamente per molti dettagli l’una dall’altra,
diciamo che sono strettamente accoppiate.
(Principi SOLID)
44. Refactoring (Design Improvement)
Rifattorizzare indica la procedura che porta a migliorare il codice in termini di:
● Prestazioni
● Memoria consumata
● Stile e comprensione
Senza di fatto modificare il comportamento visto dall'esterno.
Si fa perché, col passare del tempo, la manutenzione del codice e l’aggiunta di nuove
funzionalità provocano la perdita di coesione e aumentano l’accoppiamento. Porta a
eliminare le duplicazioni nel codice (segno di scarso design).
45. Refactoring
Il design migliora ed evolve continuamente, si pensa al cosa e
non al come e solo nella direzione necessaria, non c'è nulla di
superfluo.
Per testare serve:
● realizzare molti oggetti
● che fanno poche cose
46. Regressioni
Le suite di tests proteggono dalla regressione. I bug non appaiono all'improvviso, ed
il sistema funziona con continuità.
48. Creare nuovi test quando:
● Si aggiunge una nuova feature
● Si ha un bug (“regression test”)
● Si deve capire come funziona qualcosa (“learning test”)
● Per spiegare il funzionamento di qualcosa (i test sono documentazione)
Ma soprattutto, I test devono essere scritti prima del codice da testare, non lo
testerete mai dopo...
49. Come scrivere un test
Prima di iniziare, fate una lista di tutti i test che dovrete scrivere:
● Consente di tenere traccia di cosa c’è fare.
● Permette di concentrarsi su una cosa alla volta.
● Si tiene sotto controllo l’avanzamento.
Scegliete i dati di input per i test con accuratezza, il test è anche documentazione.
Isolati e che garantiscano la località degli errori.
Rendere chiara la relazione tra dati di ingresso e di uscita.
Separati dal codice applicativo (es. package diversi).
50. Ottimizzare i test
● Se far funzionare il test richiede troppo tempo, dividerlo in più parti.
● Se i test usano una risorsa difficile da creare/usare/gestire, utilizzare un “Mock
Object”.
● Scrivi prima il test!
● Se lavori in team lascia sempre i test funzionanti altrimenti puoi fare commit
anche con barre rosse!
● Se un test fallisce, devi avere un problema: se due test falliscono devi avere due
problemi.
● I test devono essere indipendenti dall'ordine in cui si lanciano.
51. Ne vale la pena?
Il testing porta via circa metà del tempo dello sviluppo...
Ma:
● Il tempo totale di sviluppo diminuisce.
● Il tempo di debugging diminuisce drasticamente.
● Quando scavi col debugger nel codice non sai se finirai tra 1 minuto o tra 2
giorni.
● Il costo di debugging è ritenuto di gran lunga la componente principale nella
formazione del costo dei moderni progetti di software
source: Evans Data Corporation (2012)
53. Testing e fasi di sviluppo
● Ripetizione dei test precedenti quando si localizzano e rimuovono
difetti
Maintenance
● Controllo di coerenza progetto/implementazione
● Esecuzione dei test sul prodottoCoding
● Controllo di coerenza progetto/requisiti
● Valutazione dell’architettura di sistema
● Test del design
● Generazione di dati di test funzionali e strutturali
Design
● Generazione dei dati di test
● Determinazione della strategia di test
● Test dei requisiti e delle specifiche
Analysis
Attività di testFase
54. Quindi...
Se ben progettati e mantenuti, i test aiutano a confinare i bug nella “zona verde”,
ovvero con forti caratteristiche di località:
● I bug hanno caratteristiche tali da manifestarsi immediatamente e palesemente.
● Il difetto nel codice è ricercabile in un numero di linee non eccessivamente
elevato e comunque ben identificabili.
● Le esecuzioni che manifestano il bug non sono mai troppo lunghe e/o troppe
complesse: in particolare non devono esserlo dal momento in cui l’errore si
verifica a quello in cui si manifesta
58. JUnit
Scritto da Erich Gamma and Kent Beck.
Open Source.
Supporta asserzioni, suite test e report result.
Integrato in tantissimi IDE e tool di build (ANT, Maven, SonarCube...).
59. I metodi assert / fail
Servono a verificare determinate condizioni (assert) o a segnalare un esito negativo
(fail).
Esistono di diversi tipi assert:
● assertNotNull
● assertEquals
● assertTrue, assertFalse
● ecc. ecc. ecc.
Ognuno degli assert/fail con svariati tipi di parametri di input (Stringe, numeri,
oggetti...). Il primo parametro è quello atteso, il secondo è quello ottenuto dal test
dell’oggetto.
60. Unit Best Practices 1/4
● Separare il codice di test da quello di produzione. Es: utilizzo di cartelle sorgenti
diverse.
● Compilare sempre il codice di test insieme al codice del progetto, questo
mantiene allineata la loro integrazione durante lo sviluppo.
● Anche per i test sono disponibili le tecniche dei linguaggi OO (subclassing,
helper, ecc.ecc.)
● Se la classe sotto test si chiama XXX, allora gli si associa una classe di test
chiamata XXXTest (o xxxTestCase) nello stesso package.
61. Unit Best Practices 2/4
● I test devono essere indipendenti dal tempo (indimentendi da quado vengono
eseguiti).
● Indipendenti dalle impostazioni di localizzazione/nazionalità del Sistema
Operativo.
● Descritti attraverso la documentazione del codice (JavaDoc) se necessario.
62. Unit Best Practices 3/4
Non assumere un ordine preciso per l’esecuzione dei test, ma se necessario creare
una TestSuite inserendo nell’ordine i metodi di test:
63. Unit Best Practices 4/4
Non caricare le risorse dal file system con percorsi hard-coded:
FileInputStream inp ("C:TestDatadataSet1.dat");
64. Tutto bello… ma...
● Come testare una classe che richiede una risorsa esterna?
● Come testare degli eventi?
● Come testare delle eccezioni?
● Come verificare delle interazioni complesse tra più classi / moduli?
● Come testare Servlet ed EJB?
● Come testare un plugin per Eclipse?
● Come testare…
●
65. Test Double
Per creare test più efficacemente viene fatto uso di oggetti creati appositamente
per essere utilizzati dal codice sottoposto a verifica. Questi oggetti, in base al
loro scopo, vengono chiamati:
● Dummy
● Fake
● Stubs
● Spies
● Mocks
66. Mock Object
● E' un oggetto finto che si comporta come l'originale.
● È più veloce.
● Esistono vari tools che ne facilitano la creazione.
● Aiutano a disaccoppiare le logiche.
70. Behavior-Driven Development
● Estende la filosofia dal TDD anche al personale non tecnico.
● Quindi aumenta la collaborazione tra tutte le persone
coinvolte nel progetto.
● Usa un linguaggio naturale per formulare scenari e relativi
esiti.
71. Cucumber
È il principale tool per implementare il BDD.
Basa il suo funzionamento su uno stile di linguaggio chiamato Gherkin.
Esistono versioni per moltissime piattaforme / linguaggi di programmazione.
72. Cucumber feature
Feature: Todo app
Scenario: Add todo entry
Given Todo list is empty
When I add entry "Code something"
Then the number of todo entries should be 1
Scenario: Remove todo entry
Given Todo list is empty
When I add entry "Remove me"
Then the number of todo entries should be 1
When I remove entry "Remove me"
Then the todo list should be empty
73. Cucumber Glue Code
public class TodoSteps {
private TestDesigner designer;
private HttpClient todoListClient;
@Given("^Todo list is empty$")
public void empty_todos() {
designer.http().client(todoListClient).send().delete("/api/todolist");
designer.http().client(todoListClient).receive().response(HttpStatus.OK);
}
@When("^I add entry "([^"]*)"$")
public void add_entry(String todoName) {
designer.http().client(todoListClient)
.send().post("/todolist")
.contentType("application/x-www-form-urlencoded")
.payload("title=" + todoName);
78. Continuous integration
“Is a software development practice where members of a team integrate their work
frequently, at least daily - leading to multiple integrations per day. Each integration
is verified by an automated build (including test) to detect integration errors as
quickly as possible. Many teams find that this approach leads to significantly reduced
integration problems and allows a team to develop cohesive software more rapidly.“
Martin Flower
79. C.I.: punti salienti
● Build ad ogni commit.
● Test automatici eseguiti ad ogni commit.
● Build veloci e incrementali attraverso la verifica del log del source code repo.
● Tutti possono vedere cosa accade nel processo di build.
● Minimizza la probabilità di errori nella fase di integrazione.