Progetto accademico per il corso di Integrazione e Test di Sistemi Software (ITSS) - A.A. 2023/2024
UniversitΓ degli Studi di Bari Aldo Moro
- Descrizione
- Team
- Tecnologie
- Risultati Complessivi
- Homework 1: KebabCase Converter
- Homework 2: Even Index Finder
- Quick Start
- Struttura del Progetto
- Documentazione
- Apprendimenti
TestMaster Γ¨ un progetto di testing completo che esplora diverse metodologie di test del software attraverso due case study pratici. Il progetto dimostra l'applicazione di tecniche avanzate di testing su metodi Java, con particolare attenzione a:
- Specification-Based Testing: progettazione di test basati sui requisiti
- Structural Testing: analisi della copertura del codice
- Property-Based Testing: testing automatizzato con generazione casuale di dati
- Mutation Testing: valutazione della qualitΓ della test suite
- Laura Croce - 717847 - l.croce2@studenti.uniba.it
- Sonia Auciello - 719769 - s.auciello1@studenti.uniba.it
- Luigi Dicataldo - 718152 - l.dicataldo2@studenti.uniba.it
| Tecnologia | Versione | Scopo |
|---|---|---|
| Java | 17+ | Linguaggio di programmazione |
| JUnit 5 | 5.10.0 | Framework di testing |
| JaCoCo | 0.8.11 | Code coverage analysis |
| PITest | 1.15.8 | Mutation testing |
| jqwik | 1.7.4 | Property-based testing |
| Maven | 3.8+ | Build automation |
| Metrica | Homework 1 | Homework 2 |
|---|---|---|
| Line Coverage | 100% (21/21) | 100% (18/18) |
| Branch Coverage | 100% (14/14) | 100% (24/24) |
| Mutation Score | 100% (9/9) | N/A |
| Test Cases | 17 | 1000+ |
| Test Duration | ~80ms | ~1.5s |
Testing completo del metodo convertToKebabCase() che converte stringhe in formato kebab-case sostituendo delimitatori specificati con trattini (-).
public String convertToKebabCase(String input, boolean toLowerCase, char[] delimiters)Parametri:
input: stringa da convertiretoLowerCase: setrue, converte tutto in minuscolodelimiters: array di caratteri da sostituire con-
Comportamento:
- Se
delimitersΓ¨nullo vuoto β usa spazio come default - Sostituisce i delimitatori con
- - Rimuove trattini multipli consecutivi
- Rimuove spazi iniziali/finali
- Restituisce
nullse input Γ¨null
convertToKebabCase("Hello World", true, null) // β "hello-world"
convertToKebabCase("Another Test", false, new char[]{' '}) // β "Another-Test"
convertToKebabCase("Hello_World", true, new char[]{'_'}) // β "hello-world"
convertToKebabCase("Hello, World!", true, new char[]{' ', ','}) // β "hello-world!"
convertToKebabCase(" Spaced Out ", true, null) // β "spaced-out"1οΈβ£ Specification-Based Testing
-
Comprensione dei Requisiti
- Analisi approfondita delle specifiche
- Identificazione dei parametri e output attesi
- Documentazione del comportamento previsto
-
Identificazione delle Partizioni
-
Input String:
- Stringa
null - Stringa vuota
"" - Stringa di lunghezza 1
- Stringa di lunghezza > 1
- Stringa
-
toLowerCase:
trueβ conversione in minuscolofalseβ mantiene case originale
-
delimiters:
nullβ usa spazio come default- Array vuoto
{}β usa spazio come default - Array con 1 elemento
- Array con piΓΉ elementi
-
-
Boundary Value Analysis
- Stringhe ai limiti (vuote, singolo carattere)
- Array di delimitatori ai limiti (null, vuoto, 1 elemento)
- Casi speciali (solo delimitatori, spazi multipli)
| # | Input | toLowerCase | Delimiters | Output | Tipo |
|---|---|---|---|---|---|
| 1 | "Simple test" |
true |
{' '} |
"simple-test" |
Base |
| 2 | "Another Test" |
false |
{' '} |
"Another-Test" |
Base |
| 3 | "Hello World" |
true |
null |
"hello-world" |
Default |
| 4 | null |
true |
null |
null |
Edge |
| 5 | "" |
true |
null |
"" |
Edge |
| 6 | "A" |
true |
null |
"a" |
Boundary |
| 7 | "-----" |
true |
{'-'} |
"-----" |
Edge |
| 8 | " Spaced Out " |
true |
{' '} |
"spaced-out" |
Boundary |
| 9 | "HelloWorld" |
true |
{} |
"helloworld" |
Edge |
| 10 | "Hello_World" |
true |
{'_'} |
"hello-world" |
Base |
| 11 | "123 456 789" |
true |
null |
"123-456-789" |
Special |
| 12 | "CittΓ e natura" |
true |
{' '} |
"cittΓ -e-natura" |
Unicode |
| 13 | "Math+Physics=Science" |
true |
{'+','='} |
"math-physics-science" |
Special |
| 14 | "(Hello), [World]!" |
true |
{' ',',','(',')','[',']'} |
"hello-world!" |
Complex |
| 15 | (Stringa 1000 char) | true |
{' '} |
(Output 1000 char) | Stress |
| 16 | "Oneβ¦TwoβThree" |
true |
{'.','β'} |
"one-two-three" |
Unicode |
| 17 | "γγγ«γ‘γ― δΈη" |
true |
{' '} |
"γγγ«γ‘γ―-δΈη" |
Unicode |
2οΈβ£ Structural Testing & Code Coverage
βββββββββββββββββββββββββββββββββββββββββ
β CODE COVERAGE RESULTS β
β ββββββββββββββββββββββββββββββββββββββββ£
β Line Coverage: 100% (21/21) β
β Branch Coverage: 100% (14/14) β
β Complexity: 8 β
βββββββββββββββββββββββββββββββββββββββββ
| Metodo | Lines | Branches | Complexity |
|---|---|---|---|
convertToKebabCase() |
100% | 100% | 8 |
Tutte le 14 branch sono coperte:
- β Input null check
- β Delimiters null check
- β Delimiters empty check
- β toLowerCase true/false
- β Special characters handling
- β Trim operations
- β Multiple delimiter replacement
3οΈβ£ Mutation Testing (PITest)
βββββββββββββββββββββββββββββββββββββββββ
β MUTATION TESTING RESULTS β
β ββββββββββββββββββββββββββββββββββββββββ£
β Mutation Score: 100% (9/9) β
β Killed: 9 β
β Survived: 0 β
β No Coverage: 0 β
βββββββββββββββββββββββββββββββββββββββββ
| Mutatore | Mutazioni | Uccise | Sopravvissute |
|---|---|---|---|
| CONDITIONALS_BOUNDARY | 1 | 1 | 0 |
| EMPTY_RETURNS | 1 | 1 | 0 |
| FALSE_RETURNS | 1 | 1 | 0 |
| INCREMENTS | 1 | 1 | 0 |
| INVERT_NEGS | 1 | 1 | 0 |
| MATH | 1 | 1 | 0 |
| NEGATE_CONDITIONALS | 1 | 1 | 0 |
| NULL_RETURNS | 1 | 1 | 0 |
| TRUE_RETURNS | 1 | 1 | 0 |
Cosa significa?
- Ogni mutatore modifica il codice in modo specifico
- I test devono "uccidere" le mutazioni rilevando il comportamento errato
- 100% significa che tutti i test hanno rilevato tutte le mutazioni
Durante il testing sono emersi 2 bug nel codice originale:
// Input
convertToKebabCase("-----", true, new char[]{'-'})
// Output Errato (prima del fix)
"-"
// Output Corretto (dopo il fix)
"-----"
// Fix Applicato
if (kebabCase.replaceAll("-", "").isEmpty()) {
return input; // Preserva stringhe solo con delimitatori
}// Input
convertToKebabCase(" Test ", true, null)
// Output Errato (prima del fix)
"--test--"
// Output Corretto (dopo il fix)
"test"
// Fix Applicato
input = input.trim(); // Rimuove spazi prima della conversioneTesting avanzato del metodo findEvenIndex() utilizzando Property-Based Testing con jqwik per generazione automatica e massiva di test data.
public int findEvenIndex(List<Integer> numbers, boolean findFirst)Parametri:
numbers: lista di numeri interi da analizzarefindFirst:trueβ cerca il primo numero parifalseβ cerca l'ultimo numero pari
Comportamento:
- Restituisce l'indice del numero pari trovato
- Restituisce
-1se non trova numeri pari - Solleva
IllegalArgumentExceptionse:numbersènullnumbersè vuotanumberscontiene solo numeri positivinumberscontiene solo numeri negativi
findEvenIndex([1, 2, 3, 4, 5], true) // β 1 (indice di 2)
findEvenIndex([1, 2, 3, 4, 5], false) // β 3 (indice di 4)
findEvenIndex([1, 3, 5, 7], true) // β -1 (nessun pari)
findEvenIndex([1, 3, 5, 7], false) // β -1 (nessun pari)
findEvenIndex(null, true) // β IllegalArgumentException
findEvenIndex([], true) // β IllegalArgumentException
findEvenIndex([1, 2, 3], true) // β IllegalArgumentException (solo positivi)
findEvenIndex([-1, -2, -3], true) // β IllegalArgumentException (solo negativi)Cos'Γ¨ il Property-Based Testing?
A differenza del testing tradizionale dove scrivi manualmente ogni test case, il Property-Based Testing:
- Definisci proprietΓ generali che devono essere sempre vere
- jqwik genera automaticamente centinaia/migliaia di input casuali
- Verifica che la proprietΓ sia rispettata per tutti gli input
- Se trova un errore, riduce automaticamente l'input al minimo che causa il fallimento (shrinking)
Testing Tradizionale:
@Test
void testFirstEven() {
assertEquals(1, findEvenIndex([1,2,3], true));
assertEquals(2, findEvenIndex([1,3,4], true));
assertEquals(0, findEvenIndex([2,3,4], true));
// ... altri 10-20 casi scritti manualmente
}Property-Based Testing:
@Property
void testFirstEven(@ForAll List<Integer> numbers, @ForAll boolean findFirst) {
int result = findEvenIndex(numbers, findFirst);
// jqwik testa automaticamente 1000+ combinazioni diverse!
}Le Tre ProprietΓ Testate
@Property(generation = GenerationMode.RANDOMIZED)
void testInvalid(@ForAll("invalidLists") List<Integer> numbers,
@ForAll boolean findFirst)ProprietΓ : Il metodo deve sempre lanciare IllegalArgumentException per input non validi.
Input Generati:
- Liste vuote
[] - Liste
null - Liste con solo numeri positivi
[1, 2, 3] - Liste con solo numeri negativi
[-1, -2, -3]
Risultato Atteso: Sempre IllegalArgumentException
Test Eseguiti: 1000
@Property(generation = GenerationMode.RANDOMIZED)
void testFail(@ForAll("oddsOnly") List<Integer> oddNumbers,
@ForAll boolean findFirst)ProprietΓ : Se la lista contiene solo numeri dispari, il risultato Γ¨ sempre -1.
Input Generati:
- Liste di soli numeri dispari
- Dimensione: 2-100 elementi
- Range: [-99, 99]
- Garantito mix di positivi e negativi
Generatore Personalizzato:
@Provide
Arbitrary<List<Integer>> oddsOnly() {
return Arbitraries.integers().between(-99, 99)
.filter(n -> n % 2 != 0) // Solo dispari
.list().ofMinSize(2).ofMaxSize(100)
.filter(list -> {
boolean hasPositive = list.stream().anyMatch(n -> n > 0);
boolean hasNegative = list.stream().anyMatch(n -> n < 0);
return hasPositive && hasNegative;
});
}Risultato Atteso: Sempre -1
Test Eseguiti: 1000
Statistiche Raccolte:
Distribuzione Positivi/Negativi:
ββ Numeri Positivi: 53.8%
ββ Numeri Negativi: 46.2%
@Property(generation = GenerationMode.RANDOMIZED)
void testPass(@ForAll @Size(min=20, max=50)
@IntRange(min=-100, max=100) List<Integer> numbers,
@ForAll boolean findFirst)ProprietΓ : Se esistono numeri pari, il metodo restituisce l'indice corretto.
Input Generati:
- Liste di 20-50 elementi
- Numeri nel range [-100, 100]
- Mix casuale di pari e dispari
- Garantito mix di positivi e negativi
Verifica:
// Metodi ausiliari per verifica
private int findFirstEven(List<Integer> numbers) {
for (int i = 0; i < numbers.size(); i++) {
if (numbers.get(i) % 2 == 0) return i;
}
return -1;
}
private int findLastEven(List<Integer> numbers) {
for (int i = numbers.size() - 1; i >= 0; i--) {
if (numbers.get(i) % 2 == 0) return i;
}
return -1;
}
// Nel test
int expected = findFirst ? findFirstEven(numbers) : findLastEven(numbers);
assertEquals(expected, actual);Test Eseguiti: 1000
Statistiche Raccolte:
Distribuzione Pari/Dispari:
ββ Numeri Pari: ~50%
ββ Numeri Dispari: ~50%
Dimensione Media Liste: 35 elementi
Range Effettivo: [-100, 100]
Structural Testing & Coverage
βββββββββββββββββββββββββββββββββββββββββ
β CODE COVERAGE RESULTS β
β ββββββββββββββββββββββββββββββββββββββββ£
β Line Coverage: 100% (18/18) β
β Branch Coverage: 100% (24/24) β
β Complexity: 8 β
βββββββββββββββββββββββββββββββββββββββββ
| Metodo | Lines | Branches | Complexity |
|---|---|---|---|
findEvenIndex() |
100% | 100% | 8 |
Statistiche e Analisi
jqwik raccoglie automaticamente statistiche durante l'esecuzione:
ββββββββββββββββββββββββββββββββββββββ
β INVALID INPUT STATISTICS β
β βββββββββββββββββββββββββββββββββββββ£
β Tentativi: 1000 β
β Successi: 1000 (100%) β
β Fallimenti: 0 β
β Dim. media liste: 3.2 elementi β
ββββββββββββββββββββββββββββββββββββββ
Distribuzione per Tipo:
ββ Liste vuote: 33.2%
ββ Solo positivi: 33.4%
ββ Solo negativi: 33.4%
ββββββββββββββββββββββββββββββββββββββ
β FAIL STATISTICS β
β βββββββββββββββββββββββββββββββββββββ£
β Tentativi: 1000 β
β Successi: 1000 (100%) β
β Fallimenti: 0 β
β Dim. media liste: 42.5 elementi β
ββββββββββββββββββββββββββββββββββββββ
Distribuzione Positivi/Negativi:
βββββββββββββββββββββββββββββββββββ
β ββββββββββββββββββββββββ 53.8% β Positivi
β ββββββββββββββββββββββββ 46.2% β Negativi
βββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββ
β PASS STATISTICS β
β βββββββββββββββββββββββββββββββββββββ£
β Tentativi: 1000 β
β Successi: 1000 (100%) β
β Fallimenti: 0 β
β Dim. media liste: 35.0 elementi β
ββββββββββββββββββββββββββββββββββββββ
Distribuzione Pari/Dispari:
βββββββββββββββββββββββββββββββββββ
β ββββββββββββββββββββββββ 50.1% β Pari
β ββββββββββββββββββββββββ 49.9% β Dispari
βββββββββββββββββββββββββββββββββββ
Esempi di Input Generati:
β’ [12, -45, 78, -23, 44, 91, -66, 33, 18, ...]
β’ [-88, 77, -44, 55, -22, 11, 66, -99, 44, ...]
β’ [3, -8, 15, -42, 27, -56, 31, 64, -19, ...]
I test producono automaticamente visualizzazioni:
- Istogramma Positivi/Negativi (Test FAIL)
- Grafico Combinato Pari/Dispari con frequenze (Test PASS)
- Distribuzione Dimensioni Liste
Disponibili in: target/jqwik-reports/
| Aspetto | Testing Tradizionale | Property-Based Testing |
|---|---|---|
| Test Cases | Scritti manualmente (10-50) | Generati automaticamente (1000+) |
| Tempo scrittura | Alto (ore) | Basso (minuti) |
| Copertura | Casi previsti | Esplora casi imprevisti |
| Bug Detection | Dipende dall'esperienza | Trova edge cases nascosti |
| Manutenzione | Alta | Bassa |
| Confidence | Media | Alta |
Con Testing Tradizionale avresti dovuto scrivere:
- ~10 test per input validi
- ~5 test per input non validi
- ~5 test per casi limite
- Totale: ~20 test scritti manualmente
Con Property-Based Testing hai scritto:
- 3 proprietΓ generali
- jqwik ha eseguito 3000+ test automaticamente
- Maggiore copertura con meno codice
Java JDK 17 o superiore
Maven 3.8+
IDE (consigliato: IntelliJ IDEA)# Clona il repository
git clone https://github.com/LauraCroce/TestMaster.git
cd TestMastercd homework1
# Esegui tutti i test
mvn test
# Genera report di coverage (JaCoCo)
mvn jacoco:report
# Report β target/site/jacoco/index.html
# Esegui mutation testing (PITest)
mvn org.pitest:pitest-maven:mutationCoverage
# Report β target/pit-reports/index.html
# Esegui tutto insieme
mvn clean test jacoco:report org.pitest:pitest-maven:mutationCoveragecd homework2
# Esegui property-based tests
mvn test
# Genera report di coverage
mvn jacoco:report
# Report β target/site/jacoco/index.html
# Con statistiche dettagliate
mvn test -Djqwik.reporting=trueTestMaster/
βββ README.md # Questa documentazione
βββ .gitignore # File da escludere
β
βββ homework1/ # Specification & Structural Testing
β βββ src/
β β βββ main/java/homework1/
β β β βββ KebabUtil.java # Classe da testare
β β β βββ Main.java # Esempio utilizzo
β β βββ test/java/homework1/
β β βββ KebabUtilTest.java # Test base
β β βββ KebabUtilParameterizedTest.java # Test parametrizzati
β βββ pom.xml # Configurazione Maven
β βββ target/
β βββ site/jacoco/ # Report JaCoCo
β βββ pit-reports/ # Report PITest
β
βββ homework2/ # Property-Based Testing
β βββ src/
β β βββ main/java/homework2/
β β β βββ EvenIndex.java # Classe da testare
β β βββ test/java/homework2/
β β βββ EvenIndexTest.java # Property-based tests
β β βββ EvenIndexTestCase.java # Test tradizionali
β βββ pom.xml
β βββ target/
β βββ site/jacoco/
β βββ jqwik-reports/ # Report jqwik
β
βββ docs/
βββ ITSS_Documentazione_2324.pdf # Relazione completa
- Relazione Tecnica Completa (PDF) - 44 pagine di analisi dettagliata
- Test Cases Excel - Documentazione test case con template
- JavaDoc - Documentazione del codice (generabile con
mvn javadoc:javadoc) - Coverage Reports - Report JaCoCo e PITest
- Posizione:
target/site/jacoco/index.html - Contenuto:
- Line coverage per classe
- Branch coverage
- Complexity metrics
- Codice sorgente annotato
- Posizione:
target/pit-reports/index.html - Contenuto:
- Mutation score
- Mutazioni per mutator type
- Codice mutato
- Test che hanno ucciso le mutazioni
- Posizione:
target/jqwik-reports/ - Contenuto:
- Statistiche distribuzione dati
- Grafici e istogrammi
- Shrinking examples
- Edge cases trovati
Specification-Based vs Structural Testing:
- Il testing black-box (specification-based) Γ¨ fondamentale per verificare i requisiti
- Il testing white-box (structural) garantisce copertura del codice
- Entrambi sono necessari per un testing completo
Lesson Learned:
100% code coverage non garantisce assenza di bug. I boundary cases hanno rivelato bug che la coverage da sola non avrebbe trovato.
Prima di PITest:
- Test suite con 17 test
- 100% line e branch coverage
- Fiducia nella qualitΓ : Alta
Dopo PITest:
- Mutation score: 100% (9/9 mutazioni uccise)
- Conferma: I test rilevano effettivamente errori
- Fiducia nella qualitΓ : Molto Alta β
Lesson Learned:
Mutation testing Γ¨ essenziale per validare la qualitΓ dei test, non solo la quantitΓ .
Vantaggi Concreti:
- β Trovato edge case imprevisto: lista con solo zeri
- β Verificato comportamento su 3000+ combinazioni
- β Ridotto tempo di scrittura del 80%
- β Aumentata fiducia grazie alle statistiche
Quando usarlo:
- β Logica complessa con molti casi possibili
- β Algoritmi matematici
- β Funzioni pure (stesso input β stesso output)
- β UI testing
- β I/O operations
- β Comportamenti non deterministici
Lesson Learned:
Property-based testing non sostituisce i test tradizionali, ma li complementa efficacemente per una copertura piΓΉ ampia.
Cosa abbiamo documentato:
- β Ogni test case con input/output atteso
- β Decisioni di design
- β Bug trovati e fix applicati
- β Metriche e risultati
PerchΓ© Γ¨ importante:
- Facilita manutenzione futura
- Aiuta altri sviluppatori a capire le scelte
- Dimostra processo sistematico
- Essenziale per progetti accademici e professionali
Tools utilizzati e loro valore:
| Tool | Valore Aggiunto | DifficoltΓ Apprendimento |
|---|---|---|
| JUnit 5 | Framework standard, ben documentato | Bassa |
| JaCoCo | Visualizzazione immediata coverage | Bassa |
| PITest | Valutazione qualitΓ test | Media |
| jqwik | Automazione test massiva | Alta |
| Maven | Build automation standardizzata | Media |
Lesson Learned:
Investire tempo nell'apprendimento dei tool giusti ripaga ampiamente nel lungo periodo.
1. π Analisi Requisiti
β
2. π― Identificazione Partizioni & Boundary Cases
β
3. βοΈ Scrittura Test Cases (Specification-Based)
β
4. π» Implementazione Codice
β
5. π§ͺ Esecuzione Test & Debug
β
6. π Coverage Analysis (JaCoCo)
β
7. π¦ Mutation Testing (PITest)
β
8. π Property-Based Testing (jqwik)
β
9. π Documentazione Risultati
β
TDD (Test-Driven Development) - Test scritti prima del codice
β
Continuous Testing - Test eseguiti ad ogni modifica
β
Code Review - Revisione incrociata del codice
β
Version Control - Git per tracciabilitΓ modifiche
β
Documentation First - Documentazione contestuale al codice
β
Automated Reporting - Report automatici di qualitΓ
Questo Γ¨ un progetto accademico completato. Non sono accettati contributi esterni, ma sentitevi liberi di:
- π Fork per scopi educativi
- β Star se trovate il progetto utile
- π§ Contattarci per domande o chiarimenti
Questo progetto Γ¨ stato sviluppato per scopi accademici presso l'UniversitΓ degli Studi di Bari.
Per domande, chiarimenti o collaborazioni:
Laura Croce
π§ l.croce2@studenti.uniba.it
π GitHub
πΌ LinkedIn
Made with β€οΈ and β by Team TestMaster