Software Testing - Dispensa ITSS PDF

Document Details

Uploaded by Deleted User

ITSS

Tags

software testing testing concepts software development IT

Summary

This document provides an introduction to Software Testing, covering basic definitions like errors, bugs, and failures. It describes different testing approaches, such as specification-based and structural-based testing, and illustrates how to identify and design test cases based on program requirements. It also explains the importance of testing different inputs for various outputs.

Full Transcript

Software Testing ================ What is SW Testing? ------------------- Il Testing del Software consiste in tutte quelle attività che comprendono la pianificazione, preparazione e valutazione di prodotti software per: 1. Determinare se soddisfano i requisiti specificati 2. Dimostrare che son...

Software Testing ================ What is SW Testing? ------------------- Il Testing del Software consiste in tutte quelle attività che comprendono la pianificazione, preparazione e valutazione di prodotti software per: 1. Determinare se soddisfano i requisiti specificati 2. Dimostrare che sono mirati allo scopo 3. Trovare difetti nel codice Definizioni di Base ------------------- Error: Le persone fanno errori, un buon sinonimo è sbaglio. Le persone commettono errori quando scrivono il codice, il risultato di questi errori creano Bug Bug (aka. difetto/guasto): Esso è, come detto, il risultato di un errore, più precisamente è che un guasto è la rappresentazione di un errore, esempio: errore in un testo, diagramma UML, codice sorgente, ecc. Failure: Un fallimento accade quando il codice contenente un bug viene eseguito Incident: Sintomo che un fallimento è avvenuto. Se si verifica, bisogna eseguire un'investigazione Test: Il Testing è la fase di esecuzione del software attraverso casi di test. Un test ha due obiettivi distinti: l'individuazione di guasti e la dimostrazione della corretta esecuzione del prodotto Test case: Il caso di test ha una sua identità ed è associato ad un comportamento del programma. Un test case ha un set di input e un set di output attesi Test Case --------- L'essenza del software testing è quella di determinare un set di test cases per il prodotto da testare ed è da riconoscere come un prodotto del lavoro. Un caso di test deve comprendere di queste informazioni - Un identificatore - Una descrizione che ne contestualizzi lo scopo - Una descrizione di precondizioni - Il set di Input da testare - Gli output attesi - Una descrizione di eventuali ed aspettate postcondizioni - Storico di Esecuzione: - La data di esecuzione - La persona che l'ha eseguito - La versione del sistema - Pass/Fail result Identificare i Casi di Test --------------------------- Per dominio di input di un sistema intendiamo tutte le possibili combinazioni/tipi di Input per quel sistema. Anche per un piccolo sistema, il dominio di input è talmente grande che è praticamente impossibile testare tutti i possibili input. Per questo dobbiamo identificare i casi di test (essenzialmente andare a selezionare un set finito di valori dal dominio di input per testare il sistema), si possono seguire due strategie diverse: - Functional / Specification-based Testing - Structural / Code-based Testing ### Specification-based Testing Il motivo per il quale si chiami Specification-based è perché ogni programma deve essere considerato come una funzione che mappi i valori di un certo dominio di input nel suo range di output. Per questo in questo tipo di testing si considerano i sistemi come delle Black-boxes. L'implementazione del sistema di black-box non è nota ma la funzione che esso esegue è interpretabile attraverso gli input e gli output. Per questo le sole informazioni usate sono le specifiche del software Come vantaggi abbiamo: - I test sono indipendenti dal contesto di implementazione del software (quindi se l'implementazione cambia, non abbiamo il bisogno di cambiare i test) - Il caso di test può essere sviluppato parallelamente all'implementazione della funzione Come svantaggi abbiamo. - Possibili pezzi di software non testato - Ridondanze ### Structural-based Testing (Code-Based) L'approccio code-based è fondamentale per l'identificazione di casi di test. Al contrario del black-box testing, infatti per questo chiamato anche white-box testing, è che ci si basa unicamente sull'implementazione del codice per l'identificazione dei casi di test. La Code Coverage, infatti, permette come metrica di andare a visualizzare quanto codice è coperto dal test e soprattutto se comprende tutte le strade possibili che il codice può eseguire ### Specification-based vs. Code-based Testing La vera risposta è che nessuno degli approcci è sufficiente a sé stesso: - Se qualche requisito specifico non è stato implementato, utilizzando il white-box testing non permetterà mai di riconoscerlo - Invece se il programma implementa comportamenti che non sono stati specificati a documentazione, questi non verranno mai rivelati dal testing per specifiche Bisogna utilizzare una combinazione dei due approcci Error & Fault ------------- Le definizioni di errori e guasti hanno bisogno di essere distinti attraverso la definizione di processo e prodotto: - Il Processo è come noi andiamo a fare qualcosa - Il Prodotto è il risultato di quel processo Il Software Quality Assurance è di migliorare il prodotto andando a migliorare il processo riducendo gli errori commessi nel processo di sviluppo. Invece il testing va a concentrarsi più sull'individuazione dei difetti nel prodotto. I difetti hanno diverse classificazioni: a partire da si è verificato l'errore nello sviluppo, le conseguenze di questi fallimenti, difficoltà nella risoluzione, fino al rischio di mancata risoluzione e così via. Livelli di Testing (V-model) ---------------------------- I livelli di testing riflettono i livelli di astrazione attribuiti nel modello a cascata per il ciclo di vita del software, cosicché si possano avere obiettivi chiari e distinti per i rispettivi livelli. Una variazione del modello a cascata è il V-model, dove si va ad evidenziare la corrispondenza tra il design e il testing di un sistema, notare come i 3 livelli di definizione (specification, preliminary design, detailed design) corrispondono ai tre livelli di testing: system, integration e unit testing. Immagine che contiene testo, diagramma, linea, schermata Descrizione generata automaticamente Specifiation-based Testing ========================== Requirements ------------ I Requisiti funzionali sono l'informazione più importante per questo approccio di testing, poiché ci va a definire cosa un software deve o non deve fare. Come esempi di requirements possiamo avere: Agile User Stories, UML use cases, plain text, ecc. Quando dobbiamo ricavare i test attraverso questo approccio andiamo a carpire informazioni attraverso i requirements. Partiamo con un esempio: method substringBetween() -\> Cerca in una stringa delle sottostringhe delimitate da un start e end tag, restituisce tutte le sottostringhe corrispondenti in un array public static String\[\] stringsBetween(final String str, final String open, final String close) INPUTS: - str: The string containing the substrings. Null returns null; an empty string returns another empty string. - open: The string identifying the start of the substring. An empty string returns null. - close: The string identifying the end of the substring. An empty string returns null. OUTPUTS: - The program returns a string array of substrings, or null if there is no match. Code Implementation: public static String\[\] substringsBetween(final String str, final String open, final String close) { if (str == null \|\| isEmpty(open) \|\| isEmpty(close)) { return null; } int strLen = str.length(); if (strLen == 0) { return EMPTY\_STRING\_ARRAY; } int closeLen = close.length(); int openLen = open.length(); List\ list = new ArrayList\(); int pos = 0; while (pos \< strLen - closeLen) { int start = str.indexOf(open, pos); if (start \< 0) { break; } start += openLen; int end = str.indexOf(close, start); if (end \< 0) { break; } list.add(str.substring(start, end)); pos = end + closeLen; } if (list.isEmpty()) { return null; } return list.toArray(EMPTY\_STRING\_ARRAY); } Testing Workflow for Specification-based Testing ------------------------------------------------ 1. Capire i requirments (cosa il programma deve fare, gli input e gli output) 2. Esplorare cosa il programma fa per differenti input 3. Capire gli input, output e identificare le partizioni 4. Identificare i boundary cases (casi limite) 5. Ideare i casi di test (output: Test Plan) 6. Automatizzare i casi di test (output: JUnit code) 7. Arricchire la test suite con creatività ed esperienza Importante è che non è possibile testare tutte le possibili combinazioni di input (molto spesso non è né conveniente ne efficace), un testing esaustivo deve essere sostituito da un approccio pragmatico. ### Understanding the requirements, input and outputs I Requisiti sono divisi in tre parti: la prima riguarda ciò che il programma deve fare (le sue regole di business), la seconda sono i dati che riceve come input, essi sono importanti per il nostro ragionamento poiché è attraverso questi che possiamo ricavare i vari casi di test. Infine, come ultimo abbiamo l'output che ci permette di capire meglio cosa fa il programma e come gli input vengano trasformati in output Per il metodo substringBetween(): 1. Il ***goal*** di questo metodo è quello di collezionare tutte le possibili sottostringhe che sono delimitate da un open e close tag 2. Il metodo riceve tre ***parametri***: a. str, che rappresenta la stringa dal quale bisogna ricavare le sottostringhe b. open tag, che indica l'inizio della sottostringa c. close tag, che indica la fine della sottostringa 3. Il programma ***restituisce*** un array composto da tutte le sottostringhe trovate dal metodo ### Explore what the program does for various inputs Un'esplorazione ad hoc di quel che può fare il metodo può aumentare la conoscenza di come funziona, soprattutto se non si è l'autore del codice scritto. Quindi si procede con il provare varie combinazioni di input fino a quando non si ha ben chiara in mente il modello di come funziona il programma (anche se non si va a vedere subito i casi limite in quanto si procede in un passaggio successivo su questo aspetto). Esempio: ![](media/image2.png)  ### Explore possible inputs and outputs, and identify partitions Il numero di input e di output possono essere praticamente infiniti, si deve cercare di trovare una soluzione per prioritizzare e selezionare un subset di input e output che ci danno la certezza della correttezza del funzionamento del programma. Ad esempio, per il nostro metodo substringBetween() se andassimo a testare la stringa "abcd" con open tag "a" e close tag "d" ci viene restituito "bc", se provassimo invece "xyzw" con open tag "x" e close tag "w" ci verrebbe restituito "yz". Come notabile anche se andiamo a cambiare le lettere, il programma esegue lo stesso comportamento per entrambi gli input. Quindi dati dei costraint per le risorse, si testa solo uno di questi input, non importa quale, ci interessa solo che rappresenta un'intera classe di input. Nella terminologia di testing, si dice che due input sono ***equivalenti***. Una volta identificata la classe (o partizione) di input, procedi con gli stessi passi a trovare un'altra classe di input che porterà un altro tipo di comportamento del programma che non sono stati testati. Continuando a dividere il dominio si arriva alla conclusione di avere tutte le possibili classi di input. Esempio inputs: +-----------------------+-----------------------+-----------------------+ | str parameter: | open parameter: | close parameter: | | | | | | 1. null string | 1. null string | 1. null string | | | | | | 2. Empty string | 2. Empty string | 2. Empty string | | | | | | 3. String of lenght | 3. String of lenght | 3. String of lenght | | = 1 | = 1 | = 1 | | | | | | 4. String of | 4. String of | 4. String of | | lenght \> 1 | lenght \> 1 | lenght \> 1 | | | | | | (any string) | (any string) | (any string) | +-----------------------+-----------------------+-----------------------+ Ovviamente non serviranno tutte le combinazioni possibili, andremo a ricavare solo le combinazioni utili al testing. Può capitare che i vari input siano ***interdipendenti*** l'uno con l'altro, come in questo caso: +-----------------------------------------------------------------------+ | (str, open, close) parameters: | | | | 1. str non contiene ne open tag ne il close tag | | | | 2. str contiene open tag ma non il close tag | | | | 3. str contiene il close tag ma non l'open tag | | | | 4. str contiene entrambi open e close tag | | | | 5. str contiene entrambi open e close tag più volte | +-----------------------------------------------------------------------+ Dopo di che si riflette sui possibili outputs. In questo caso il metodo restituisce un array di sottostringhe: si possono trovare due tipi di outputs differenti uno è il vettore stesso e l'altro per le stringhe presente nell'array: +-----------------------------------+-----------------------------------+ | Array of strings (output) | Each individual string (output) | | | | | a. null array | a. Empty | | | | | b. Empty array | b. Single character | | | | | c. Single item | c. Multiple character | | | | | d. Multiple item | | +-----------------------------------+-----------------------------------+ ### Analyze the boundaries Molto spesso i bugs si nascondono nei casi limite del dominio di input, come ad esempio l'uso degli operatori di comparazione in un if. I programmi con questo tipo di bugs tendono a funzionare bene quando usiamo determinati input, ma se ci avviciniamo ai casi limite l'input fallisce. Quando andiamo a definire partizioni, essi hanno casi limite stretti con le altre partizioni. Un esempio è un programma che stampa "hiphip" per un numero più piccolo di 10 e "hooray" per un numero più grande o uguale a 10. Un tester divide il dominio in 2 partizioni: un set di input che stampino "hiphip" e un set di input che stampino "hooray". Quindi lo scopo del boundary testing è andare ad assicurarsi che il programma funzioni correttamente quando ci troviamo in un caso limite. Quando ci troviamo in un caso limite, è bene testare il programma su entrambi i punti di boundary: - ***ON point***: è il punto che si trova sul boundary - ***OFF point***: è il punto più vicino al boundary ma che si trovi nell'altra partizione Esempio: if (a \>= 10), **10 è l'ON point** e **9 è l'OFF point** Dopo di che ci sono altre 2 suddivisioni nei casi limite: - ***IN points***: sono i punti che rendono una condizione vera - ***OUT points***: sono i punti che rendono una condizione falsa Esempio: 10, 11, 12, 25, 42 sono tutti esempi di IN points in quanto rendono la condizione if (a \>= 10) vera. D'altro canto 8, 9, 0, -25 sono tutti esempi di OFF points in quanto rendono la condizione falsa ### Devise test cases Adesso si passa a elencare quali saranno i test da effettuare, idealmente si potrebbe pensare di combinare tutti i casi, il problema sovviene quando i test combinati diventano tanti che in molti casi non sarebbero significativi per garantire il corretto funzionamento del programma. Si decide ragionando, quindi, quali partizioni devono essere combinati e quali no. Innanzitutto si procede a prendere un solo test di casi eccezionali e di non combinarli, esempio le partizioni ***null o empty string*** le testiamo solo una volta e quindi: - T1: str is null - T2: str is empty - T3: open is null - T4: open is empty - T5: close is null - T6: close is empty Poi si procede con le altre: - Per le stringhe di lunghezza 1 ne ricaviamo solo 4: - T7: Singolo carattere in str corrispondente al open tag - T8: Singolo carattere in str corrispondente al close tag - T9: Singolo carattere in str non corrispondente ne al open tag ne al close - T10: Singolo carattere in str corrispondente sia al open che al close tag - Una prima combinazione di input (str length \> 1, open & close length =1): - T11: str does not contain either the open or the close tag. - T12: str contains the open tag but does not contain the close tag. - T13: str contains the close tag but does not contain the open tag. - T14: str contains both the open and close tags. - T15: str contains both the open and close tags multiple times. - Poi (str length \> 1, open length \> 1, close length \> 1): - T16: str does not contain either the open or the close tag. - T17: str contains the open tag but does not contain the close tag. - T18: str contains the close tag but does not contain the open tag. - T19: str contains both the open and close tags. - T20: str contains both the open and close tags multiple times - Infine i casi limite: - T21: str contains both the open and close tags with no characters between them. ### Automate the test cases Adesso si trasformano i test progettati nel linguaggio di testing da utilizzare (esempio JUnit) ### Augment the test suite with creativity and experience Essere sistematici è bene, ma non bisogna mai scordare la propria esperienza. In questo step dobbiamo guardare le partizioni che abbiamo trovato e capire se si possono sviluppare variazioni particolari, perché è sempre bene trovare delle variazioni nel testing. Esempio non abbiamo pensato di poter testare delle stringhe con spazi all'interno Structural testing and code coverage ==================================== Passaggi per effettuare lo Structural Testing --------------------------------------------- 1. Si percorrono i passi definiti nel Specification-based testing 2. Si legge l'implementazione, c ercando di capire le decisioni nel codice prese dallo sviluppatore 3. Si esegue la test suite con uno strumento di code coverage per poter identificare quali parti di codice non sono stati inclusi nel test 4. Per ogni pezzo di codice che non è stato coperto: a. Perché non è stato coperto? b. Decidere se quel codice ha bisogno di essere testato (se si prosegui) c. Implementare un test case adatto 5. Tornare al punto 3 Quindi, come possibile vedere, lo Structural testing è un complemento per lo specification-based testing in quanto va a completare il testing sul codice Criteri di Code Coverage ------------------------ Quando troviamo una riga non coperta dobbiamo capire con quanta rigorosità andare a coprire il codice, facciamo un esempio: if (!Character.isLetter(str.charAt(i)) && (last == \'s\' \|\| last == \'r\')) I criteri possono essere: 1. **Line Coverage**: La riga è considerata coperta se almeno un singolo test percorre quella suddetta riga (in totale 1 test). Per calcolare qual è la percentuale di line coverage si fa: \ [\$\$line\\ coverage = \\frac{\\text{lines\\ covered}}{\\text{total\\ number\\ of\\ lines}} \\times 100\\%\$\$]{.math.display}\ 2. **Branch Coverage**: Quando si percorre un qualsiasi blocco di codice con condizione e si testa sia la strada vera che la strada falsa (in totale 2 test). Per calcolare la percentuale si fa: \ [\$\$branch\\ coverage = \\frac{\\text{branches\\ covered}}{\\text{total\\ number\\ of\\ branches}} \\times 100\\%\$\$]{.math.display}\ 3. **Condition + Branch Coverage:** Questa modalità comprende sia il branch coverage (quindi testare sia quando la condizione è vera o falsa), ma anche tutte le possibili condizioni presenti all'interno dell'istruzione (in questo esempio abbiamo 3 condizioni, quindi 3\*2=6 test totali). Per calcolare la percentuale si fa: \ [\$\$c + b\\ coverage = \\frac{branches\\ covered\\ + \\ conditions\\ covered}{number\\ of\\ branches\\ + \\ number\\ of\\ conditions} \\times 100\\%\$\$]{.math.display}\ 4. **Path Coverage**: E' il criterio più rigoroso in quanto deve coprire tutti i possibili percorsi di esecuzione del programma. (In questo caso con 3 condizioni che possono prendere 2 possibili strade, abbiamo in tutto 2^3^ test = 8 test totali. Per calcolare le possibili strade da coprire: [path coverage (n conditions) = 2^*n*^]{.math.inline} Complex conditions and the MC/DC coverage criterion --------------------------------------------------- Riuscire a massimizzare le identificazioni di bugs mentre si tiene basso il costo e l'effort nel costruire la test suite è compito di ogni tester. La domanda è quanto complesso possono diventare delle condizioni molto lunghe? La risposta è utilizzare il criterio Modified Condition /Decition Coverage (MC/DC). Il criterio MC/CD cerca le combinazioni di condizioni come la path coverage fa. Invece che riuscire a testare tutte le possibili combinazioni identificate nella condizione, si testano solo le combinazioni importanti. Nelle condizioni abbiamo un risultato binario (o True o False), quindi il numero totale di test per arrivare al 100% della Mc/DC coverage è N +1, dove N è il numero di condizioni della nostra decisione. ### Creare una test suite per MC/DC Adesso bisogna capire come selezionare (meccanicamente) le condizioni da testare. Facciamo un esempio con if (A && (B \|\| C)). Se dobbiamo applicare il path coverage abbiamo 2^3^ test da fare, invece con MC/DC abbiamo 3 +1 = 4 test da eseguire. Per aiutarci creiamo la tabella delle verità di questa espressione (come si farebbe per trovare i test da path coverage): Test case A B C Result ----------- ------- ------- ------- -------- T1 True True True True T2 True True False True T3 True False True False T4 True False False False T5 False True True False T6 False True False False T7 False False True False T8 False False False False Per determinare quali dei quattro test mi soddisfano MC/DC, per ogni condizione dovremmo trovare: - Un caso nel quale la condizione è True (T1) - Un caso nella quale la condizione è False (T2) - T1 e T2 devono avere risultati diversi (uno true e uno false) - Le altre condizioni in T1 devono avere gli stessi valori in T2 T1 e T2 si chiamano coppie indipendenti, perché la variabile A influenza indipendentemente il risultato finale della condizione. Quindi come coppie di test che rispecchiano questi requisiti sono: - isLetter: {1, 5}, {2, 6}, {3, 7} - last == s: {2, 4} - last == r: {3, 4} Adesso quali coppie dobbiamo scegliere per ogni variabile? Per le ultime due condizioni è automatico in quanto abbiamo i test T2, T3, T4. Invece quali dovremmo prendere per la prima variabile? Scegliamo un tipo di test che abbia già uno dei test presi per gli altri in maniera tale da non sforare con gli N +1 test da poter scegliere. Esempio: Per isLetter, se prendiamo la coppia {1, 5}, finiremmo per avere 5 test da implementare (T1, T2, T3, T4, T5); mentre se scegliamo tra {2, 6} e {3, 7} non fanno differenza in quanto alla fine avremo comunque 4 test (come previsto) alla fine che sono meglio di 8 (path coverage): - {2, 6} -\> T2, T3, T4, T6 - {3, 7} -\> T2, T3, T4, T7 Gestire Cicli e costrutti simili -------------------------------- Quando arriviamo a testare dei cicli, come while o for, il codice all'interno del blocco di iterazione può ripetersi per un numero differente di volte, rendendo il testing più complicato. Riuscire a dare una risposta esaustiva è impossibile, i tester si rivolgono spesso al loop **boundary adequacy criterion** per decidere quando stoppare il test di un loop. La test suite per un loop soddisfa questo criterio se e solo se per ogni ciclo: - C'è un caso di test che esegue il ciclo 0 volte - Un caso di test che esegue il ciclo una volta - Un caso di Test che esegue il ciclo più volte Il problema sussiste nel caso di test per eseguire il ciclo più volte, non c'è un numero preciso di volte che deve essere eseguito. La decisione sovviene dopo un'attenta analisi del programma e dei suoi requisiti; non ci sono problemi se vengono creati anche più casi di test per i cicli, basta che si assicuri che il ciclo funzioni come previsto Quale criterio assumere? ------------------------ ![Immagine che contiene testo, schermata, Carattere, diagramma Descrizione generata automaticamente](media/image4.png) Mutation Testing ---------------- I criteri prima discussi mostrano quanto del codice di produzione viene testato. Ma quello che manca a questi metodi è se comunque questi test sono abbastanza buoni e resistenti affinché catturino altri bugs. Se introducessimo un bug intenzionalmente nel codice, il test si rompe? Come già detto la coverage da sola non è sufficiente a tal punto da determinare se una test suite è esaustiva. Adesso andiamo a vedere la capacità di fault detection di una test suite, quanti bug può rivelare? La risposta dietro questa domanda è il Mutation Testing, ovvero inserire appositamente nel codice un bug e verificare se esso venga riconosciuto dalla test suite. Se nulla succede e il test passa a buon fine, anche con il bug dentro, significa che c'è qualcosa da migliorare nel codice. Il coupling effect dice che un bug complesso è causato da una combinazione di piccoli bug. Quindi se la tua test suite può riconoscere questi piccoli bug, allora siamo sicuri che riesca ad individuare anche quelli più complessi. I mutatori (mutants) possono essere: - Conditionals boundary: operatori relazionali come \< oppure \ - **Property-Based Testing (PBT)**: - Si definiscono proprietà generali che il programma deve rispettare. - Non si scelgono manualmente esempi specifici: il framework genera input casuali (seguendo restrizioni definite) per validare le proprietà. - **Vantaggi**: - Copertura più ampia: verifica su un numero elevato di input, compresi casi limite (edge cases). - Riduzione della dipendenza dagli esempi specifici. - Automazione del processo di generazione degli input. - **Esempio**: - Per lo stesso programma di valutazione, si definiscono le seguenti proprietà: - Per voti \< 5.0, il risultato deve essere false. - Per voti ≥ 5.0, il risultato deve essere true. - Per voti fuori dal range \[1.0, 10.0\], deve essere lanciata un\'eccezione. **Benefici del PBT** -------------------- - **Copertura Ampia**: I test coprono una grande varietà di input, esplorando scenari che potrebbero non essere stati considerati dallo sviluppatore. - **Individuazione di Edge Case**: Il framework genera input limite, spesso difficili da prevedere manualmente. - **Efficienza**: Il processo è automatizzato, riducendo il tempo richiesto per scrivere test. - **Manutenzione Ridotta**: Poiché si definiscono proprietà generali, i test sono meno dipendenti da dettagli specifici Processo di Property-Based Testing ---------------------------------- ### Passaggi principali: - **Definizione delle proprietà**: - Identificare le proprietà generali che il programma o il metodo deve rispettare. - Le proprietà derivano dai requisiti funzionali. - **Generazione degli input**: - Utilizzare un framework (es. **jqwik**) per generare automaticamente una vasta gamma di input casuali. - Applicare restrizioni per generare solo dati validi (es. numeri positivi, stringhe di una certa lunghezza). - **Esecuzione dei test**: - Il framework esegue il metodo sotto test con gli input generati. - Se una proprietà non è rispettata, il framework segnala il primo input che causa un errore (detto **controesempio**). Esempi Pratici -------------- ### Esempio 1: Programma di valutazione dei voti - **Specifiche**: - Voti validi: da 1.0 a 10.0. - Voti ≥ 5.0: successo (true). - Voti \< 5.0: fallimento (false). - Voti fuori range: eccezione. - **Property-Based Testing**: - Proprietà \"fail\": Per voti tra 1.0 (incluso) e 5.0 (escluso), il risultato deve essere false. - Proprietà \"pass\": Per voti tra 5.0 (incluso) e 10.0 (incluso), il risultato deve essere true. - Proprietà \"invalid\": Per voti fuori dal range, deve essere lanciata un\'eccezione. **Implementazione con jqwik**: - Si utilizza l\'annotazione **\@Property** per definire un metodo di test che rappresenta una proprietà. - Si usa **\@ForAll** per specificare che il parametro del metodo deve essere popolato con valori casuali generati automaticamente. ### Esempio 2: Metodo unique - **Obiettivo**: Testare un metodo che restituisce un array con valori univoci in ordine decrescente. - **Proprietà definite**: - L'array restituito non deve contenere duplicati. - Gli elementi devono essere ordinati in ordine decrescente. - **Approccio**: - Generare array casuali. - Validare le proprietà sopra elencate su ciascun array generato jqwik: Un Framework per il PBT in Java -------------------------------------- ### Funzionalità principali: - **Generazione di input casuali**: - jqwik supporta diversi generatori per tipi comuni (Stringhe, Numeri, Liste, ecc.). - Gli input possono essere filtrati o combinati per rispettare restrizioni specifiche. - **Edge Case**: - jqwik genera automaticamente casi limite per verificare la robustezza del metodo. - **Statistiche**: - È possibile raccogliere statistiche sugli input generati per analizzare la distribuzione. ### Annotazioni principali: - **\@Property**: Indica che un metodo rappresenta una proprietà da testare. - **\@ForAll**: Specifica che il parametro deve essere popolato con input casuali. Sfide del PBT ------------- - **Creatività**: È necessario definire proprietà che coprano tutte le specifiche rilevanti. - **Distribuzione degli input**: Gli input devono essere rappresentativi dei casi reali. - **Filtraggio**: Combinazioni non valide di input devono essere eliminate. Test Double & Mocks =================== Un **Test Double** è un oggetto o un componente che sostituisce un componente reale durante l'esecuzione di un test. Questi oggetti vengono utilizzati per simulare il comportamento di altre classi o dipendenze, al fine di facilitare il testing di unità di codice in isolamento. Quando si testano classi che dipendono da altre, si possono incontrare diversi problemi: - **Input indiretti imprevedibili**: Ad esempio, una dipendenza che utilizza l'orologio di sistema o un calendario. - **Dipendenze non disponibili**: Alcune risorse potrebbero non esistere nell'ambiente di test (es. un database esterno o un'API). - **Output difficili da monitorare**: Gli effetti collaterali prodotti dalla dipendenza possono essere complessi o costosi da recuperare. - **Prestazioni**: Testare una classe insieme alle sue dipendenze reali può essere lento o complicato. **Soluzione**: Utilizzare i **Test Doubles**, che ci consentono di simulare queste dipendenze, avendo un controllo totale sul loro comportamento. Tipi di Test Doubles -------------------- ### Dummy Objects - Oggetti segnaposto passati come parametri, ma mai realmente utilizzati nei test. - **Esempio**: Una classe Customer che accetta Address ed Email come parametri, ma durante il test utilizza solo l'indirizzo. L'oggetto email è un dummy. ### Fake Objects - Oggetti con un'implementazione semplificata rispetto a quella reale. - **Esempio**: Una classe FakeDB che utilizza una ArrayList per simulare un database invece di interagire con un vero sistema di gestione dei dati. ### Stubs - Forniscono risposte predefinite (hardcoded) ai metodi chiamati durante il test, senza implementare una vera logica. - **Uso**: Vengono utilizzati per fornire input indiretti validi o invalidi alla classe in test. - **Esempio**: Un metodo getAllInvoices() di uno stub può restituire una lista predefinita di fatture senza interagire con un database reale. ### Mocks - Simili agli stubs, ma con funzionalità aggiuntive: - Registrano tutte le interazioni con gli altri oggetti. - Permettono di verificare quante volte un metodo è stato chiamato e con quali parametri. - **Esempio**: Testare quante volte è stato invocato il metodo sendInvoice() in una classe che invia fatture. ### Spies - Osservano il comportamento di un oggetto reale. - Registrano tutte le chiamate ai metodi dell'oggetto, senza simulare il comportamento. - **Differenza rispetto ai Mocks**: Gli spies non simulano il comportamento dell'oggetto, ma si limitano a monitorarlo. Framework di Mocking: Mockito ----------------------------- Mockito è uno dei framework più popolari per la creazione di mock e stub in Java. Permette di simulare facilmente dipendenze complesse e verificare le interazioni. ### Funzionalità principali di Mockito - **Creazione di mock**: Crea un mock della classe specificata. - MyClass mock = mock(MyClass.class); - **Definizione del comportamento**: Definisce il comportamento di un metodo del mock. - when(mock.method()).thenReturn(value); - **Verifica delle interazioni**: Verifica che un determinato metodo sia stato chiamato. - verify(mock).method(); - **Gestione delle eccezioni**: Configura il mock per lanciare un'eccezione. - doThrow(new Exception()).when(mock).method(); Stubbing delle Dipendenze ------------------------- Lo **stubbing** è una tecnica utilizzata nel testing software per sostituire una dipendenza reale con una sua versione semplificata (stub) che restituisce risultati predefiniti. Questa tecnica consente di isolare la classe o il metodo sotto test, garantendo che i risultati dei test non siano influenzati da comportamenti imprevedibili o complessi delle dipendenze reali. ### Obiettivi dello Stubbing - **Isolamento**: Permette di testare una classe senza dipendere da altre componenti. - **Controllo**: Fornisce risultati predefiniti, eliminando incertezze dovute a dipendenze esterne (es. database o API). - **Efficienza**: Rende i test più veloci ed evita costi di configurazione per infrastrutture complesse. - **Test di casi eccezionali**: Simula facilmente scenari difficili da riprodurre, come il lancio di eccezioni. ### Benefici dello Stubbing - **Semplicità**: - Non è necessario configurare un database o altre infrastrutture. - **Isolamento**: - Il test verifica solo il comportamento di InvoiceFilter, senza dipendere dal funzionamento di IssuedInvoices. - **Flessibilità**: - Possiamo configurare lo stub per simulare scenari specifici (es. restituire una lista vuota o lanciare un'eccezione). Approfondimenti su Mocks e Stubs -------------------------------- ### Mocks vs Stubs - **Stubs**: - Utilizzati per restituire valori predefiniti. - Adatti a testare scenari in cui il comportamento della dipendenza è prevedibile. - **Mocks**: - Registrano interazioni e permettono verifiche dettagliate. - Ideali quando vogliamo monitorare il numero di chiamate o i parametri passati. Best Practices per l'uso dei Test Doubles ----------------------------------------- - **Quando usare i mocks**: - Dipendenze lente o difficili da configurare (es. database, web service). - Dipendenze che comunicano con infrastrutture esterne. - Simulazione di scenari eccezionali (es. lanciare un'eccezione). - **Quando NON usare i mocks**: - Dipendenze semplici o facili da manipolare. - Metodi di utilità o funzionalità basilari (es. librerie standard come ArrayList). - **Wrappers**: - Incapsulare le dipendenze difficili da mockare (es. LocalDate.now()) in una classe wrapper (es. Clock) per facilitarne il mock. - **Non mockare tipi esterni**: - È preferibile creare un'astrazione sopra le librerie esterne per semplificare il mock e mantenere il controllo. Sfide nell'uso dei Mocks ------------------------ - **Coupling**: - I test diventano più legati al codice di produzione, aumentando la probabilità di rottura con cambiamenti futuri. - **Fedele rappresentazione**: - I mocks devono rispettare i contratti delle dipendenze reali; altrimenti, potrebbero produrre risultati non accurati. - **Complessità**: - Un uso eccessivo di test doubles può rendere i test difficili da comprendere e mantenere. Static testing ============== Il Testing Statico è un tipo di metodo di Software Testing eseguito per individuare i difetti nel software senza eseguire effettivamente il codice dell\'applicazione (come avviene invece nel Testing Dinamico). Il Testing Statico (noto anche come Software Reviews) può essere considerato una forma di \"testing umano\". **Perché?** - La lettura del codice è una parte fondamentale di un processo di testing completo. - Il testing del software mira a individuare i difetti causando errori durante l\'esecuzione. - Alcuni tipi di Software Reviews (ad esempio, l\'ispezione del codice) cercano di identificare difetti che, una volta eseguiti, causano malfunzionamenti. **Quando?** - Prima di iniziare il testing basato su esecuzioni al computer. Static Testing VS Dynamic Testing {#static-testing-vs-dynamic-testing.ListParagraph} --------------------------------- **Testing Statico**: - Non richiede l\'esecuzione del codice. - Può essere eseguito manualmente o utilizzando un insieme di strumenti. **Testing Dinamico**: - Comporta l\'esecuzione del software e la valutazione del suo comportamento durante l\'esecuzione (ad esempio, **Unit Testing**, **Integration Testing**, **System Testing** e **Acceptance Testing**). I **vantaggi del \"testing umano\"** includono: - Gli errori vengono trovati prima (riducendo i costi). - All\'inizio c\'è meno pressione sui programmatori: - i programmatori commettono più errori se un difetto viene trovato più tardi. Analisi Statica --------------- **L\'Analisi Statica** include la valutazione della qualità del codice. Vengono utilizzati strumenti diversi per analizzare il codice e confrontarlo con gli standard. Inoltre, aiuta nell\'identificazione dei seguenti difetti: - **Variabili inutilizzate** - **Codice morto** - **Loop infiniti** - **Variabili con valore non definito** - **Sintassi errata** Tipi di revisioni del software ------------------------------ - **Ispezione del codice (Code Inspection)**: Una revisione formale del codice da parte di un gruppo di esperti, mirata a identificare difetti, problemi di qualità e violazioni degli standard. - **Revisione del codice (Code Walkthrough)**: Una presentazione informale del codice da parte dello sviluppatore a un gruppo di colleghi, con l\'obiettivo di raccogliere feedback e identificare potenziali miglioramenti. - **Verifica a scrivania (Desk Checking)**: Un processo informale in cui il programmatore rivede il proprio codice da solo, cercando di identificare errori o miglioramenti senza l\'aiuto di strumenti automatizzati o colleghi. - **Valutazione tra pari (Peer Rating)**: Una valutazione del codice o del lavoro di uno sviluppatore da parte dei suoi pari, basata su criteri di qualità, efficienza e conformità agli standard. - **Audit**: Una revisione formale e indipendente del software o del processo di sviluppo, eseguita per garantire la conformità a normative, standard e best practice. - **Programmazione in coppia (Pair Programming)**: Due sviluppatori lavorano insieme su una stessa postazione di lavoro, uno scrive il codice mentre l\'altro revisiona e fornisce feedback in tempo reale. Revisione del software ---------------------- È un test durante il quale un prodotto di lavoro (ad esempio, un documento o del codice) viene valutato da una o più persone per rilevare problemi come *code smells*, difetti, ecc. (ovviamente, l\'obiettivo principale è rilevare difetti). Esistono due approcci principali per le revisioni del codice: 1. **Ispezione del codice (Code Inspection)**: Una revisione formale e dettagliata del codice, condotta da un gruppo di esperti che esaminano attentamente il codice per individuare difetti, violazioni degli standard e aree di miglioramento. 2. **Revisione del codice (Code Walkthrough)**: Un processo informale in cui lo sviluppatore presenta il proprio codice a un gruppo di colleghi per discutere e ottenere feedback su potenziali problemi, miglioramenti e difetti. ### Code Inspection & Walkthrough: - Possono essere utilizzati in **qualsiasi fase dello sviluppo del software**, dopo che un sistema o un componente/unità è stato ritenuto completo. - Entrambi comportano un **team di persone** che legge o ispeziona visivamente il codice. - Questo team partecipa a una riunione con l\'obiettivo di **individuare problemi**, ma non di **trovare soluzioni** a questi problemi (è un processo di **test**, non di **debugging**!). Code Inspection --------------- Un\'**ispezione del codice** è un insieme di procedure e tecniche per la rilevazione di problemi durante la lettura del codice da parte di un gruppo. **Peculiarità dell\'ispezione del codice**: - Viene utilizzato una **checklist** per i problemi. - Consiste in una riunione **ininterrotta**, che dovrebbe durare da **90 a 120 minuti**. - Il tasso di codice valutato di solito è di **150 dichiarazioni per ora**. Se un sistema software è troppo grande per essere esaminato in una riunione di **90-120 minuti**, è possibile pianificare **più riunioni di ispezione del codice**. Un\'ispezione del codice di solito consiste di **quattro persone**: - **Il moderatore**: - È un programmatore competente, ma **non l\'autore** del codice da valutare, e non è necessario che conosca tutti i dettagli del sistema. - I compiti del moderatore includono: - Distribuire il materiale per la riunione e programmare l\'incontro di ispezione. - Condurre la riunione. - Registrare tutti i problemi trovati. - Assicurarsi che i problemi vengano successivamente risolti. - **Il produttore** (l\'autore del codice da valutare): - È la persona che ha scritto il codice che viene esaminato. - **Gli altri due membri** di solito sono: - **Il progettista del sistema**: Una persona che ha contribuito alla progettazione del sistema e che porta una visione sul design. - **Un specialista dei test**: Una persona che dovrebbe essere familiare con i problemi di programmazione più comuni (non solo i difetti), per aiutare a identificare problemi di qualità del codice e testabilità. ### Code Inspection Agenda Il **moderatore** guida la discussione durante la riunione e si assicura che i partecipanti concentrino la loro attenzione sull\'individuazione dei **problemi**, non sulla loro correzione. L\'obiettivo è **trovare i problemi**! Al termine della riunione, il produttore riceve una **lista dei problemi** emersi, che dovrà correggere dopo l\'incontro. Se emergono altri problemi o se questi richiedono correzioni sostanziali, il moderatore potrebbe pianificare un\'altra riunione di ispezione per **rivalutare** il codice. La lista dei problemi viene utilizzata per **aggiornare la checklist dei problemi**, al fine di migliorare l\'efficacia delle **future ispezioni**. ### Issue Checklist Ogni azienda software ha la propria **checklist dei problemi**. Alcuni problemi riguardano **errori comuni**, mentre altri possono riguardare **problemi di leggibilità** del codice, ecc. Le checklist dei problemi di solito sono organizzate in **categorie**, come ad esempio: - **Errori di sintassi**: Problemi con la grammatica del codice. - **Problemi di performance**: Codice che può essere ottimizzato. - **Problemi di design**: Incoerenze o violazioni nei principi di progettazione. - **Problemi di leggibilità**: Codice poco chiaro o difficilmente comprensibile. - **Problemi di sicurezza**: Vulnerabilità nel codice che potrebbero compromettere la sicurezza del sistema. - **Conformità agli standard**: Violazioni degli standard di codifica stabiliti. Queste categorie aiutano a sistematizzare la ricerca dei difetti e migliorano l\'efficacia delle ispezioni. ### Human aspects in code inspections Gli **aspetti umani nelle ispezioni del codice** sono cruciali per il successo del processo: - Se il **produttore** considera l\'ispezione come un attacco personale e adotta una **posizione difensiva**, il processo diventerà **inefficace**. Un ambiente di lavoro collaborativo e non giudicante è essenziale per ottenere risultati utili. - I risultati di un\'ispezione devono essere **confidenziali**, condivisi solo tra i partecipanti. - Ad esempio, non bisogna usare i risultati per **presumere che un programmatore sia inefficiente o incompetente**. - L\'obiettivo dell\'ispezione è migliorare il codice e il processo, non criticare o demotivare il team. ### Benefici collaterali delle ispezioni del codice: - **Feedback prezioso per il produttore**: Il produttore riceve **feedback utile** riguardo lo **stile di programmazione**, la **scelta degli algoritmi** e le **tecniche di programmazione**, aiutandolo a migliorare le proprie competenze. - **Acquisizione di conoscenze da parte di qualsiasi partecipante**: Ogni partecipante può apprendere durante le ispezioni, ad esempio, **stili di programmazione**, **problemi comuni** nel codice, e altre best practices. - **Incentivare a scrivere codice pulito**: Poiché il codice del produttore è soggetto a valutazione, viene **stimolato a scrivere codice più pulito** (ad esempio, **legibile** e ben strutturato), migliorando la qualità complessiva del lavoro. - **Comportamento cooperativo**: Le ispezioni favoriscono un comportamento **cooperativo** all\'interno del team, poiché tutti i membri partecipano attivamente nella ricerca e correzione dei problemi. - **Individuazione delle sezioni più soggette a errori**: Le ispezioni aiutano a identificare le **sezioni più vulnerabili** del programma, su cui si concentrerà maggiore attenzione nella fase di **testing automatizzato**, migliorando l\'efficacia dei test. In sintesi, le ispezioni non solo migliorano la qualità del codice, ma promuovono anche una cultura di apprendimento continuo e collaborazione all\'interno del team. ### Aspetti umani nelle walkthrough del codice e benefici collaterali: **Aspetti umani**: - Come nelle **ispezioni del codice**, l\'**atteggiamento dei partecipanti** è fondamentale per il successo della walkthrough. - Ad esempio, se il **produttore** vede la walkthrough come un attacco personale e adotta una **posizione difensiva**, il processo diventerà **inefficace**. Un\'atmosfera di collaborazione e apertura è essenziale per il buon esito. **Benefici collaterali**: - I benefici sono **simili a quelli delle ispezioni del codice**: - I partecipanti possono **acquisire conoscenze** durante la walkthrough, ad esempio, sull\'**identificazione delle sezioni più soggette a errori**, oltre a migliorare la comprensione di **errori comuni**, **stili di programmazione** e **tecniche**. - Le walkthrough possono anche **educare i partecipanti** sui difetti più comuni e aiutare a riconoscere schemi di programmazione che potrebbero portare a problemi in futuro. In generale, le walkthrough offrono un\'opportunità non solo per migliorare la qualità del codice, ma anche per **educare il team** e promuovere una cultura di apprendimento continuo e collaborazione. Desk checking: -------------- Può essere visto come un\'**ispezione individuale**. Una persona legge il codice da valutare, lo verifica rispetto a una **lista di problemi** e/o esegue il codice con **dati di test**. Per essere efficace, **una persona diversa dal produttore** dovrebbe eseguire il desk checking, poiché le persone sono generalmente **inefficaci nel testare i propri programmi**. ### Limitazioni rispetto a ispezioni e walkthrough: - È **meno efficace** rispetto a un\'ispezione del codice o a un walkthrough, in quanto manca il **lavoro di squadra** e la **condivisione delle conoscenze**. - In ispezioni e walkthrough, si crea un ambiente sano e **competitivo** in cui i partecipanti cercano di dimostrare la loro capacità di identificare i problemi. - Questo tipo di ambiente non è presente nel **desk checking**, dove la persona agisce da sola, limitando le opportunità di apprendimento collettivo. In sintesi, sebbene il desk checking possa essere utile, non offre gli stessi benefici di **collaborazione e apprendimento** delle ispezioni e dei walkthrough, e tende a essere meno efficace nel rilevare difetti. Peer rating: ------------ È una **revisione** il cui obiettivo non è trovare errori specifici nel codice, ma piuttosto **valutare la qualità complessiva**, la **manutenibilità**, l\'**estensibilità**, la **usabilità** e la **chiarezza** di un programma. **Processo**: 1. **Amministratore del processo**: Un programmatore viene selezionato per servire come amministratore della procedura. L\'amministratore sceglie tra **6 e 20 partecipanti** (programmmatori con background simili, ad esempio non mescolare programmatori Java con programmatori C). 2. **Scelta dei programmi da valutare**: Ogni partecipante deve selezionare **due programmi** da sottoporre a revisione: a. Uno dovrebbe rappresentare il miglior lavoro che il partecipante considera di aver fatto. b. L\'altro dovrebbe essere un programma che il partecipante considera di qualità inferiore. 3. **Distribuzione casuale**: Una volta raccolti i programmi, vengono **distribuiti casualmente** tra i partecipanti. c. Ogni partecipante riceve **quattro programmi** da recensire, ma non i propri. d. Due programmi sono quelli considerati come **migliori** e due come **più scarsi**, ma il partecipante non sa quale sia quale. 4. **Tempo e valutazione**: Ogni partecipante dedica **30 minuti** alla revisione di ciascun programma e poi completa un **modulo di valutazione** che chiede di rispondere a domande come: e. È stato facile comprendere il programma? f. Il design di alto livello era chiaro e ragionevole? g. Il design di basso livello era visibile e ragionevole? h. Sarebbe facile modificare questo programma? i. Saresti orgoglioso di aver scritto questo programma? Inoltre, viene richiesto di fornire **commenti generali** e **suggerimenti di miglioramento**. 5. **Ritorno delle valutazioni**: Dopo la revisione, i partecipanti ricevono le **valutazioni anonime** per i loro due programmi, insieme a un **riassunto statistico** che mostra la classifica complessiva e dettagliata dei loro programmi rispetto a tutti gli altri. Inoltre, vengono forniti **analisi** su come le loro valutazioni si confrontano con quelle degli altri revisori per gli stessi programmi. **Obiettivo**: L\'obiettivo del **peer rating** è permettere ai programmatori di **auto-valutare** le proprie competenze, migliorando la consapevolezza delle proprie capacità di programmazione. Questo processo risulta utile sia in **ambito industriale** che in **ambito accademico**, come in aula o in un ambiente di sviluppo software. Audit ----- Un audit è un esame **indipendente** di un prodotto di lavoro, effettuato da una **terza parte** per valutare la **conformità** a **specifiche**, **standard**, **accordi contrattuali** o altri **criteri** stabiliti. Gli audit sono generalmente utilizzati per garantire che il lavoro sia in linea con determinati requisiti e che vengano rispettati gli **standard di qualità**, le **normative legali** o altre condizioni contrattuali. Essi sono condotti da esperti esterni, al fine di assicurare un\'**analisi imparziale** del prodotto o del processo in esame. Pair programming ---------------- È una pratica associata a processi **agili** come **Extreme Programming (XP)**. In questo approccio, due programmatori scrivono il codice insieme, alternandosi alla tastiera. 1. **Ruoli**: a. **Driver**: i. Il programmatore che scrive il codice. Si concentra sui dettagli del codice, come **sintassi** e **struttura**. b. **Navigator**: ii. Il programmatore che guida il lavoro del driver. Esamina il codice scritto, segnala possibili problemi, si concentra sul **flusso del lavoro** e lo **rivede in tempo reale**. Dopo aver analizzato, discute insieme al driver soluzioni alternative. 2. **Vantaggi**: c. **Migliora la qualità del codice e la leggibilità**: Poiché entrambi i programmatori sono coinvolti nel processo, il codice tende ad essere più pulito e facilmente comprensibile. d. **Accelera il processo di revisione**: Poiché il codice viene continuamente esaminato durante la scrittura, si riducono i tempi di revisione successivi. e. **Mette insieme attività di codifica e ispezione**: Elimina il divario tra le fasi classiche di codifica e revisione del codice. f. **Responsabilità collettiva**: Il codice non è \"di proprietà\" di un singolo programmatore, ma diventa una **responsabilità condivisa** da entrambi i programmatori. 3. **Ruolo nel processo XP**: g. In **Extreme Programming**, il pair programming è una pratica quotidiana che viene eseguita durante le normali **giornate lavorative** (di solito da 8 ore al giorno). h. È fondamentale che i programmatori abbiano un atteggiamento **costruttivo** e che ci sia **armonia** tra di loro per rendere la pratica efficace. 4. **Lavoro sincrono**: i. Il pair programming richiede che i programmatori lavorino insieme in modo **sincrono**, il che significa che devono essere fisicamente o virtualmente nella stessa \"area di lavoro\" per collaborare attivamente. In generale, il pair programming è un modo potente per migliorare la qualità del codice, ridurre gli errori e favorire un ambiente di lavoro collaborativo. GitHub Copilot -------------- **GitHub Copilot** è uno strumento AI che suggerisce codice e funzioni in tempo reale direttamente nell\'editor. Introduce un tipo di **pair programming con l\'AI**, dove il focus si sposta dalla scrittura del codice alla **revisione** di quello suggerito. Questo può ridurre il tempo speso a scrivere codice, ma rischia di diminuire la comprensione di come e perché il codice funziona, rendendo più importante saper **verificare** il codice piuttosto che scriverlo da zero. Designing for testability ========================= Introduzione alla Testabilità ----------------------------- - **Definizione di testabilità**: La testabilità si riferisce alla facilità con cui è possibile scrivere test automatizzati per un software. Un software è considerato testabile se è progettato in modo tale da facilitare il processo di testing. - **Importanza della testabilità**: Progettare per la testabilità è cruciale per garantire che il software possa essere testato in modo sistematico e completo, riducendo il rischio di bug e migliorando la qualità complessiva del prodotto. Strategie per Migliorare la Testabilità --------------------------------------- 1. **Separazione del Codice di Dominio dal Codice di Infrastruttura**: a. **Problema**: Quando il codice di dominio (logica di business) e il codice di infrastruttura (dipendenze esterne come database, servizi web, ecc.) sono mescolati, diventa difficile testare il sistema. b. **Soluzione**: Utilizzare il pattern dell\'architettura esagonale (Ports and Adapters) per separare il codice di dominio dalle dipendenze esterne. Questo pattern permette di isolare la logica di business dalle implementazioni specifiche dell\'infrastruttura, facilitando il testing. 2. **Architettura Esagonale**: c. **Descrizione**: L\'architettura esagonale, anche conosciuta come Ports and Adapters, separa il dominio (logica di business) dalle dipendenze esterne attraverso l\'uso di porte (interfacce) e adattatori (implementazioni delle porte). d. **Vantaggi**: Questo approccio permette di stubbare e mockare le porte durante i test, concentrandosi sul comportamento della classe principale senza preoccuparsi delle dipendenze esterne. Controllabilità e Osservabilità ------------------------------- - **Controllabilità**: Assicurarsi che le classi siano facilmente controllabili significa che è possibile sostituire le dipendenze con mock, fake o stub. Questo permette di controllare il comportamento della classe sotto test. - **Esempio**: La classe PaidShoppingCartsBatch riceve tutte le sue dipendenze tramite il costruttore, permettendo di iniettare facilmente mock durante i test. - **Osservabilità**: Facilitare l\'osservazione del comportamento delle classi significa introdurre metodi che permettono di verificare facilmente i risultati attesi. - **Esempio**: Aggiungere un metodo isReadyForDelivery alla classe ShoppingCart per verificare se il carrello è pronto per la consegna, evitando l\'uso di spy nei test. Principio di Inversione delle Dipendenze ---------------------------------------- - **Descrizione**: Il principio di inversione delle dipendenze afferma che i moduli di alto livello (es. classi di business) non dovrebbero dipendere dai moduli di basso livello. Entrambi dovrebbero dipendere da astrazioni (interfacce). - **Vantaggi**: Le astrazioni sono meno fragili e meno soggette a cambiamenti rispetto ai dettagli di basso livello, riducendo la necessità di modificare il codice ogni volta che cambiano i dettagli di implementazione. Coesione -------- La coesione si riferisce al fatto che un modulo, una classe o un metodo abbia una sola responsabilità. Classi e metodi coesi rendono i test più semplici e riducono la complessità dei test. - **Problema**: Classi non coese hanno suite di test molto grandi e complesse, con molteplici comportamenti da testare. - **Soluzione**: Rifattorizzare le classi per renderle più coese e quindi più facili da testare. Conclusione ----------- - **Separazione del Codice**: Separare il codice di infrastruttura dal codice di dominio per facilitare i test unitari. - **Controllabilità e Osservabilità**: Garantire che le classi siano facilmente controllabili e osservabili per facilitare i test. - **Modifiche al Codice di Produzione**: A volte è necessario modificare il codice di produzione per facilitare i test, migliorando la qualità complessiva del software. Integration & System tests ========================== ### **Introduzione ai Test di Integrazione e di Sistema** {#introduzione-ai-test-di-integrazione-e-di-sistema.ListParagraph} - **Definizione di test più grandi**: I test di integrazione e di sistema sono test che esercitano più componenti del sistema insieme, verificando il comportamento complessivo e l\'interazione con le infrastrutture esterne come database e servizi web. ### **Identificazione delle Parti da Testare** {#identificazione-delle-parti-da-testare.ListParagraph} 1. **Componenti che devono essere esercitati insieme**: Per evitare che la suite di test sia troppo debole, è necessario testare il comportamento complessivo di molte classi che lavorano insieme. 2. **Componenti che comunicano con infrastrutture esterne**: È importante testare le interazioni con database, servizi web, ecc. ### **Test di Sistema** {#test-di-sistema.ListParagraph} - **Definizione**: I test di sistema verificano l\'intero sistema end-to-end. - **Esempio**: Le slide suggeriscono di esaminare un esempio dettagliato (pagine 216-224) che mostra come esercitare tutte le classi insieme scrivendo un test più grande e creando una classe factory nel codice di produzione per istanziare una classe con tutte le sue dipendenze. ### **Test di Integrazione** {#test-di-integrazione.ListParagraph} - **Definizione**: I test di integrazione verificano l\'interazione tra componenti del sistema e infrastrutture esterne. - **DAO (Data Access Object)**: Le classi DAO sono responsabili del recupero o della persistenza delle informazioni nel database. Nei test unitari, queste classi vengono mockate, ma nei test di integrazione vengono effettivamente interrogate. ### **Testing di Database e SQL** {#testing-di-database-e-sql.ListParagraph} - **Tecniche di testing**: - **Specification-based testing**: Le query SQL derivano dai requisiti e possono essere testate analizzando le partizioni equivalenti. - **Boundary analysis**: Le query SQL hanno confini che devono essere testati per rilevare bug. - **Structural testing**: Le query SQL contengono predicati e possono essere testate utilizzando la loro struttura per derivare i casi di test. - **Esempio di query SQL**: SELECT \* FROM INVOICE WHERE VALUE \ 50 AND VALUE \< 200 contiene un singolo ramo composto da due predicati. È importante considerare anche i valori nulli. ### **Altri Test SQL** {#altri-test-sql.ListParagraph} - **Casi da testare**: - Query che non selezionano alcuna riga. - Query che selezionano righe identiche (duplicati indesiderati). - Selezione di almeno due gruppi diversi per ogni GROUP BY. - Testare subquery che restituiscono zero o più righe, con almeno un valore nullo e due valori diversi nella colonna selezionata. - Testare predicati LIKE, gestione delle date, gestione delle stringhe, conversioni di tipi di dati, ecc. ### **Verifica dei Vincoli del Database** {#verifica-dei-vincoli-del-database.ListParagraph} - **Verifica dei vincoli**: Assicurarsi che il database imponga i suoi vincoli. - **Test automatizzati per query SQL**: Utilizzare JUnit per scrivere test SQL, stabilendo una connessione con il database, eseguendo la query SQL e verificando l\'output. ### **Esempio di Test SQL Automatizzati** {#esempio-di-test-sql-automatizzati.ListParagraph} - **Classe InvoiceDao**: Esegue tre azioni principali: - save(): persiste una fattura nel database. - all(): restituisce tutte le fatture nel database. - allWithAtLeast(): restituisce tutte le fatture con almeno un valore specificato. ### **Implementazione JDBC di InvoiceDao** {#implementazione-jdbc-di-invoicedao.ListParagraph} - **Esempio di query**: - save() -\> INSERT INTO invoice(name, value) VALUES(?, ?) - all() -\> SELECT \* FROM invoice - allWithAtLeast() -\> SELECT \* FROM invoice WHERE value \>= ? ### **Test per InvoiceDao** {#test-per-invoicedao.ListParagraph} - **Pulizia dello stato del database**: Prima di eseguire i test, è necessario aprire la connessione al database, pulire il database e metterlo nello stato iniziale desiderato. Dopo i test, chiudere la connessione. ### **Best Practices per i Test di Integrazione** {#best-practices-per-i-test-di-integrazione.ListParagraph} 1. **Creare una classe base**: Gestisce le connessioni e le transazioni con il database, pulendo il database e riducendo il codice nei test. 2. **Minimizzare i dati richiesti**: Assicurarsi che i dati di input siano minimizzati. 3. **Considerare l\'evoluzione dello schema**: La suite di test dovrebbe essere resiliente ai cambiamenti dello schema del database. 4. **Utilizzare un database di test**: Decidere se i test devono comunicare con lo stesso database di produzione o con un database più semplice (es. in-memory). System Tests ============ ### **Introduzione ai Test di Sistema** {#introduzione-ai-test-di-sistema.ListParagraph} I test di sistema verificano l\'intero sistema end-to-end, simulando l\'interazione dell\'utente con l\'applicazione web attraverso il browser. ### **Esempio di Applicazione Web** {#esempio-di-applicazione-web.ListParagraph} **Flusso di lavoro**: Gli utenti visitano una pagina web, il browser invia una richiesta al server, il server elabora la richiesta e restituisce una risposta, che viene visualizzata nel browser. Le interazioni degli utenti spesso innescano altre richieste e risposte. ### **Strumenti per i Test di Sistema** {#strumenti-per-i-test-di-sistema.ListParagraph} **Selenium**: Un framework noto per testare applicazioni web. Selenium può connettersi a qualsiasi browser e controllarlo, permettendo di automatizzare azioni come aprire URL, trovare elementi HTML, cliccare pulsanti, ecc. ### **Test dei Percorsi Utente** {#test-dei-percorsi-utente.ListParagraph} - **Definizione**: I test dei percorsi utente simulano il viaggio tipico di un utente attraverso il sistema, coprendo l\'intera interazione dell\'utente con il sistema per raggiungere un obiettivo. - **Esempio**: Applicazione Spring PetClinic, dove un utente può cercare proprietari, aggiungere un nuovo proprietario, aggiungere un animale domestico al proprietario e registrare una visita dal veterinario. ### **Page Objects (POs)** {#page-objects-pos.ListParagraph} - **Descrizione**: Il pattern Page Object aiuta a scrivere test web più manutenibili e leggibili. Consiste nel definire una classe che incapsula tutta la logica (Selenium) coinvolta nella manipolazione di una pagina. - **Esempio**: Se l\'applicazione ha una pagina \"List of Owners\", si creerà una classe ListOfOwnersPage che saprà come gestirla (es. estrarre i nomi dei proprietari dall\'HTML). ### **Esempio di Test di Sistema** {#esempio-di-test-di-sistema.ListParagraph} - **Find Owners Page**: Modellare la pagina \"Find Owners\" con un costruttore che riceve un WebDriver. La prima azione modellata è findOwners(), che riempie il campo \"Last Name\" con il valore passato come parametro. - **Passaggi**: - Cercare il pulsante \"Find Owner\". - Localizzare il form e il pulsante all\'interno. - Chiamare il metodo click() per cliccare il pulsante e inviare il form. - La pagina successiva mostra la lista dei proprietari con il cognome cercato. ### **Best Practices per i Test di Sistema** {#best-practices-per-i-test-di-sistema.ListParagraph} 1. **Ambiente Pulito**: Eseguire i test in un ambiente pulito con il minimo indispensabile di dati. 2. **Visita delle Pagine**: Visitare tutte le pagine che fanno parte del percorso utente sotto test. 3. **Configurazione Importante**: Passare configurazioni importanti alla suite di test (URL locale, browser web, ecc.). 4. **Test su Più Browser e Dispositivi**: Eseguire i test su più browser e dispositivi per garantire la compatibilità. ### **Analisi Costi/Benefici dei Test di Sistema** {#analisi-costibenefici-dei-test-di-sistema.ListParagraph} **Domande da Porsi**: - Quanto costa scrivere questo test? - Quanto costa eseguirlo? - Qual è il beneficio di questo test? Quali bug rileverà? - Questa funzionalità è già coperta dai test unitari? Se sì, è necessario coprirla anche con i test di integrazione? ### **Metodi Pseudo-Testati** {#metodi-pseudo-testati.ListParagraph} Metodi che sono coperti ma non testati. Se sostituiamo l\'intera implementazione con un semplice return null, i test passano comunque. È importante eseguire test unitari e test più grandi insieme per scoprire tali casi. ### **Infrastruttura per i Test di Integrazione e di Sistema** {#infrastruttura-per-i-test-di-integrazione-e-di-sistema.ListParagraph} **Importanza**: Un\'infrastruttura adeguata è fondamentale per: - Configurare l\'ambiente. - Pulire l\'ambiente. - Recuperare dati complessi. - Verificare dati complessi. - Eseguire compiti generici complessi necessari per scrivere i test. ### **Strumenti DSL per Scrivere Test** {#strumenti-dsl-per-scrivere-test.ListParagraph} Strumenti come Robot Framework e Cucumber permettono di scrivere test in un linguaggio quasi naturale, utili se si desidera che stakeholder non tecnici scrivano i test. TDD (Test Driven Development) ============================= ### **Introduzione al Test-Driven Development (TDD)** {#introduzione-al-test-driven-development-tdd.ListParagraph} **Definizione**: Il TDD è una metodologia di sviluppo software in cui si scrive prima un test e poi si implementa il codice di produzione per far passare il test. ### **Passaggi del TDD** {#passaggi-del-tdd.ListParagraph} 1. **Scrivere un test**: Rappresenta la prossima piccola funzionalità da implementare. 2. **Il test fallisce**: La funzionalità non è ancora implementata. 3. **Scrivere il codice di produzione**: Necessario per far passare il test. 4. **Rifattorizzare**: Migliorare il codice scritto. ### **Esempio di TDD: Conversione di Numeri Romani** {#esempio-di-tdd-conversione-di-numeri-romani.ListParagraph} - **Obiettivo**: Implementare un programma che riceva un numero romano (come stringa) e restituisca la sua rappresentazione nel sistema numerico arabo (come intero). - **Esempi di input e output**: - Numeri con singoli caratteri: \"I\" -\> 1, \"V\" -\> 5, \"X\" -\> 10. - Numeri composti da più caratteri senza notazione sottrattiva: \"II\" -\> 2, \"III\" -\> 3, \"VI\" -\> 6, \"XVII\" -\> 17. - Numeri con notazione sottrattiva: \"IV\" -\> 4, \"IX\" -\> 9, \"XIV\" -\> 14, \"XXIX\" -\> 29. ### **Passaggi Dettagliati dell\'Esempio** {#passaggi-dettagliati-dellesempio.ListParagraph} 1. **Scrivere il primo test**: Assicurarsi che l\'input \"I\" restituisca 1. a. Il codice del test non compila perché la classe RomanNumberConverter e il metodo convert() non esistono. Creare uno scheletro di codice. 2. **Implementare il codice di produzione**: Scrivere il codice necessario per far passare il test. 3. **Aggiungere più esempi**: Passare al prossimo esempio più semplice, come \"V\" -\> 5. 4. **Generalizzare il codice**: Utilizzare una mappa per associare i simboli romani ai loro valori interi. 5. **Test per numeri con più caratteri**: Iterare su ogni simbolo nel numero romano, accumulare il valore e restituire il valore totale. 6. **Test per la notazione sottrattiva**: Scrivere un test per la notazione sottrattiva, come \"IV\" -\> 4. b. Implementare la logica per sottrarre il valore quando un numero è più piccolo del suo vicino a destra. 7. **Rifattorizzare**: Migliorare il codice di produzione e di test dopo aver fatto passare i test. ### **Gestione dei Casi Limite** {#gestione-dei-casi-limite.ListParagraph} - **Casi non validi**: Gestire numeri non validi come \"VXL\" e \"ILV\". - **Stringhe vuote o nulle**: Gestire input vuoti o nulli. ### **Ciclo del TDD** {#ciclo-del-tdd.ListParagraph} **Ciclo red-green-refactor**: - Scrivere un test (rosso). - Implementare la funzionalità (verde). - Rifattorizzare il codice. ### **Vantaggi del TDD** {#vantaggi-del-tdd.ListParagraph} 1. **Riflettere sui requisiti**: I test riflettono i requisiti. 2. **Controllo sul ritmo**: Controllo completo sul ritmo di scrittura del codice di produzione. 3. **Feedback rapido**: Feedback rapido ad ogni passo. 4. **Codice testabile**: Si pensa al test prima di scrivere il codice di produzione. 5. **Feedback sul design**: Il codice di test è spesso il primo cliente della classe che si sta sviluppando. ### **Quando Usare il TDD?** {#quando-usare-il-tdd.ListParagraph} - **Usare il TDD**: - Quando non si ha una chiara idea di come progettare o implementare un requisito specifico. - Quando si affronta un problema complesso o si manca di esperienza per risolverlo. - **Non usare il TDD**: - Quando si conosce già il problema e la soluzione migliore. - Quando si hanno funzionalità complesse e difficili da testare. ### **TDD e Testing Efficace** {#tdd-e-testing-efficace.ListParagraph} - **Fase di sviluppo**: Il TDD viene utilizzato durante la fase di sviluppo. - **Testing sistematico**: Dopo la sessione di TDD, iniziare con il testing sistematico ed efficace. - **Combinare TDD e testing efficace**: Mescolare cicli brevi di TDD con cicli brevi di testing sistematico ed efficace. Integrazione tra Sistemi Software ================================= ### **Motivazioni** {#motivazioni.ListParagraph} - **Evoluzione dei Sistemi Aziendali**: - I sistemi aziendali sono diventati sempre più complessi e distribuiti, passando da architetture hardware/software centralizzate a distribuite. - I modelli di sviluppo software si sono evoluti da monolitici a distribuiti. - **Problemi di Integrazione**: - **Applicazioni Legacy**: Spesso non seguono le tecnologie e gli standard moderni, creando difficoltà di integrazione. - **Integrazione Punto-Punto**: Crescita esponenziale delle connessioni necessarie. - **Tecnologie Proprietarie**: Varietà di tecnologie di comunicazione, causando il fenomeno noto come \"enterprise chaos\". ### **Evoluzione dei Sistemi Informativi** {#evoluzione-dei-sistemi-informativi.ListParagraph} **Da Silos Funzionali a Sistemi a Servizi**: Transizione verso architetture orientate ai servizi per migliorare l\'integrazione e la flessibilità. ### **Tecniche e Strumenti per EAI (Enterprise Application Integration)** {#tecniche-e-strumenti-per-eai-enterprise-application-integration.ListParagraph} 1. **EAI 1.0: Hub-and-Spoke**: a. Architettura iniziale per l\'integrazione dei sistemi, basata su un hub centrale che gestisce le comunicazioni tra i vari spoke (sistemi). 2. **EAI 2.0: SOA e ESB**: b. **SOA (Service Oriented Architecture)**: i. **Modello Client-Server**: Basato su servizi autosufficienti, coesi e opachi. ii. **Componenti Software**: I servizi sono visti come black-box legati a specifiche aree di business. iii. **Framework Principali**: Introdotta nei principali framework come.NET e J2EE. c. **ESB (Enterprise Service Bus)**: iv. Facilita la comunicazione tra servizi, agendo come un intermediario che gestisce le interazioni tra i vari componenti del sistema. 3. **API e Web Services**: d. **SOAP/REST**: Standard per l\'integrazione basata su messaggi. e. **REST**: v. Successore di SOAP, utilizza HTTP per identificare e manipolare risorse tramite URL. vi. **Principi di REST**: 1. **Modello Client-Server**: Migliora la scalabilità e la portabilità. 2. **Uniformità delle Interfacce**: Standardizzazione delle interfacce. 3. **Stateless**: Ogni richiesta è indipendente dalle altre. 4. **Layered System**: Non si è a conoscenza se si sta interagendo con un endpoint o un attore. 5. **Code-on-Demand**: Il server può inviare codice da eseguire su richiesta. ### **Integrazione per Dati (The Old Way)** {#integrazione-per-dati-the-old-way.ListParagraph} **Scambio di File e Tabelle di Frontiera**: - Metodo ancora utilizzato, soprattutto in ambito enterprise, ma altamente soggetto a errori. - Può essere assimilato a una architettura produttore/consumatore. - Spesso considerata insostituibile dai reparti IT che tendono a non voler modificare procedure in esercizio da anni. ### **Scelta della Tecnologia** {#scelta-della-tecnologia.ListParagraph} **Variabili Dipendenti dal Contesto**: - La scelta della tecnologia dipende dal contesto, dal dominio e dall\'integrazione con sistemi di terze parti. - **Aspetti Fondamentali**: Sicurezza e velocità/affidabilità della comunicazione. ### **Esempi di Casi d\'Uso Reali** {#esempi-di-casi-duso-reali.ListParagraph} 1. **Integrazione per Dati**: a. **Problema**: Sincronizzare ordini cliente da e-commerce con gestionale SAP. b. **Soluzione**: Scambio di file strutturato con cadenza prefissata. c. **Criticità**: Scarso controllo sui dati, integrazione non in tempo reale, molto codice di controllo. 2. **Mobile App**: d. **Problema**: Gestione di un wallet elettronico tramite servizio di terze parti. e. **Soluzione**: Interrogazione di Webservices SOAP/REST. f. **Criticità**: Diverse tecnologie usate in sequenza, molto codice di controllo. 3. **App e Legacy System**: g. **Problema**: Gestione di un veicolo NLT tramite integrazione di servizi di terze parti. h. **Soluzione**: Interrogazione di Webservices REST. i. **Criticità**: Molti fornitori, molto codice di controllo. 4. **Portale WEB -- Ecosistema Servizi**: j. **Problema**: Vendita di un veicolo NLT tramite integrazione con servizi aziendali. k. **Soluzione**: Interrogazione di Web Services REST, ESB e tabelle di frontiera. l. **Criticità**: Diversi fornitori. 5. **Portale WEB Corporate**: m. **Problema**: Rifacimento di un portale corporate e realizzazione di un custom CMS. n. **Soluzione**: Adozione di un framework HEADLESS. o. **Criticità**: Necessità di componenti grafiche specifiche, rischio di eccessiva personalizzazione. ### **Sicurezza e Altre Considerazioni** {#sicurezza-e-altre-considerazioni.ListParagraph} - **API Key**: Gestione delle scadenze. - **Whitelist/Blacklist**: Controllo di domini, package, IP, ecc. - **Limiti e Feature**: Spesso legati al billing. Test Code Quality {#test-code-quality.ListParagraph} ================= ### **Principi del Codice di Test Manutenibile** {#principi-del-codice-di-test-manutenibile.ListParagraph} 1. **I test devono essere veloci**: a. **Motivazione**: Più velocemente otteniamo feedback dal codice di test, meglio è. Suite di test lente ci costringono a eseguire i test meno frequentemente, rendendoli meno efficaci. b. **Soluzione**: Separare i test lenti (es. test SQL) da quelli veloci. 2. **I test devono essere coesi, indipendenti e isolati**: c. **Motivazione**: Un singolo metodo di test dovrebbe testare una singola funzionalità o comportamento del sistema. Test che esercitano più funzionalità riducono la nostra capacità di comprendere cosa viene testato e rendono la manutenzione futura più difficile. d. **Soluzione**: Suddividere in più test più piccoli. I test non dovrebbero dipendere da altri test per avere successo. Rifattorizzare la suite di test in modo che ogni test sia responsabile di impostare l\'intero ambiente di cui ha bisogno. 3. **I test devono avere una ragione di esistere**: e. **Motivazione**: Aiutare a trovare bug. Non vogliamo test che aumentino solo la copertura del codice. La suite di test perfetta è quella che può rilevare tutti i bug con il minor numero di test. 4. **I test devono essere ripetibili e non instabili**: f. **Motivazione**: Un test ripetibile dà lo stesso risultato indipendentemente da quante volte viene eseguito. g. **Cause di test instabili**: i. Dipendenza da risorse esterne o condivise (es. DB). ii. Timeout impropri (comuni nei test web). iii. Interazione nascosta tra diversi metodi di test --- Il test A influenza il risultato del test B, causando possibili fallimenti. 5. **I test devono avere una singola e chiara ragione per fallire**: h. **Motivazione**: Il codice di test dovrebbe aiutare a capire cosa ha causato il bug. i. **Soluzione**: Dare ai test un nome che indichi la loro intenzione e il comportamento che esercitano. Se i valori di input sono complessi, usare buoni nomi di variabili che spiegano cosa sono e commenti nel codice in linguaggio naturale. 6. **I test devono essere facili da scrivere**: j. **Motivazione**: Ricordare le classi base di test per facilitare i test di integrazione SQL e i POs per facilitare i test web. 7. **I test devono essere facili da leggere**: k. **Motivazione**: Gli sviluppatori passano più tempo a leggere che a scrivere codice. Assicurarsi che tutte le informazioni (soprattutto gli input e le asserzioni) siano abbastanza chiare e usare costruttori di dati di test quando si costruiscono strutture di dati complesse. 8. **I test devono essere facili da modificare e evolvere**: l. **Motivazione**: Assicurarsi che modificarli non sia troppo doloroso. Considerare quanto i test sono accoppiati al codice di produzione (es. mock). ### **Code Smells** {#code-smells.ListParagraph} - **Definizione**: I code smells indicano sintomi che possono indicare problemi più profondi nel codice sorgente del sistema. Alcuni esempi noti sono Long Method, Long Class e God Class. I code smells ostacolano la comprensibilità e la manutenibilità dei sistemi software. - **Test Smells**: Anche i test smells danneggiano la manutenzione e la comprensibilità della suite di test. ### **Test Smells** {#test-smells.ListParagraph} 1. **Assertion Roulette (AR)**: Quando un metodo di test ha più asserzioni non documentate. Più asserzioni in un metodo di test senza un messaggio descrittivo impattano la leggibilità/comprensibilità/manutenibilità poiché non è possibile capire il motivo del fallimento del test. 2. **Conditional Test Logic (CTL)**: Indica il numero di metodi di test che contengono una o più istruzioni di controllo (es. if, switch, espressione condizionale, for, foreach e while). 3. **Constructor Initialization (CI)**: Idealmente, la suite di test non dovrebbe avere un costruttore. L\'inizializzazione dei campi dovrebbe essere nel metodo setUp(). 4. **Default Test**: Indica se la classe di test è chiamata \'ExampleUnitTest\' o \'ExampleInstrumentedTest\'. 5. **Duplicate Assert (DA)**: Quando un metodo di test verifica la stessa condizione più volte all\'interno dello stesso metodo di test, indica il numero di metodi di test che contengono più di una asserzione con gli stessi parametri. 6. **Eager Test (EA)**: Indica il numero di metodi di test che contengono più chiamate a metodi di produzione. 7. **Empty Test (EM)**: Quando un metodo di test non contiene istruzioni eseguibili. 8. **Exception Handling (EH)**: Un metodo di test che contiene una dichiarazione throw o una clausola catch. 9. **General Fixture**: Quando non tutti i campi istanziati nel metodo setUp di una classe di test sono utilizzati da tutti i metodi di test nella stessa classe di test. 10. **Ignored Test (IT)**: Indica il numero di metodi di test che contengono l\'annotazione \@Ignore. 11. **Lazy Test (LT)**: Indica il numero di metodi di test che chiamano lo stesso metodo di produzione. 12. **Magic Number Test (MNT)**: Le asserzioni in un metodo di test contengono letterali numerici (es. magic numbers) come parametri. I magic numbers non indicano il significato/scopo del numero. Dovrebbero essere sostituiti con costanti o variabili, fornendo così un nome descrittivo per l\'input. 13. **Mystery Guest**: Quando un metodo di test utilizza risorse esterne (es. file, database, ecc.). L\'uso di risorse esterne nei metodi di test comporterà problemi di stabilità e prestazioni. 14. **Redundant Print (RP)**: Indica il numero di metodi di test che invocano i metodi print, println, printf o write della classe System. 15. **Redundant Assertion (RA)**: I metodi di test contengono asserzioni che sono sempre vere o sempre false. 16. **Resource Optimism (RO)**: Questo smell si verifica quando un metodo di test fa un\'ipotesi ottimistica che la risorsa esterna (es. File), utilizzata dal metodo di test, esista. 17. **Sensitive Equality (SE)**: Quando il metodo toString viene utilizzato all\'interno di un metodo di test. Le modifiche all\'implementazione di toString() potrebbero causare fallimenti. 18. **Sleepy Test**: Causare esplicitamente il sonno di un thread può portare a risultati inaspettati poiché il tempo di elaborazione per un\'attività può differire su dispositivi diversi.

Use Quizgecko on...
Browser
Browser