Salta al contenuto
Note per Studenti Esercizio - casi di test con classi di equivalenza e valori limite per il limite di prestiti (progetto originale)

Esercizio - casi di test con classi di equivalenza e valori limite per il limite di prestiti (progetto originale)

Esercizio originale, nello stile del progetto d'esame (progettazione dei casi di test e test JUnit). Teoria: Software testing - progettazione dei casi di test e JUnitIl testing esegue il programma con dati artificiali per mostrare che soddisfa i requisiti (validation testing) e per scoprirne i difetti (defect testing), ma può rivelare la presenza di errori e non la loro assenza; il test di sviluppo comprende unit test (singole classi e metodi), test dei componenti (le interfacce) e test di sistema (le interazioni); i casi di test si ricavano con le classi di equivalenza (una per partizione) e i valori limite (ai bordi delle partizioni), più linee guida per sequenze e input anomali; JUnit automatizza i test con @Test, fixture @Before/@After (@BeforeClass/@AfterClass), le asserzioni (assertEquals con atteso per primo), eccezioni attese, timeout e @Ignore; nel TDD il test si scrive prima del codice (red, green, refactor) e i test automatici diventano la suite di regressione; seguono i test di rilascio (black box sui requisiti, prestazioni) e dell'utente (alpha, beta, accettazione).Software testing - progettazione dei casi di test e JUnit →; il requisito viene dalla specifica di Esercizio - specifica dei requisiti di un sistema di prestito bibliotecario (progetto originale) (S3.1). Il codice e i test sono stati compilati ed eseguiti con JDK 21 (JUnit 4, con un runner minimo: vedi la nota sul testing).

Testo

Specifica R-23: numero massimo di documenti in prestito contemporaneo.

  • L'età valida va da 6 a 120 anni compresi; fuori da questo intervallo il metodo deve lanciare IllegalArgumentException.
  • 6-13 anni: 2 documenti; 14-17: 4; 18-64: 6; 65 in su: 8.
  • La tessera premium aggiunge 2 documenti in ogni fascia.

La firma è public static int massimo(int eta, boolean premium). Si richiede di: 1) individuare le classi di equivalenza per ciascuna variabile; 2) ricavare i valori limite; 3) scrivere la tabella dei casi di test (id, ingresso, atteso, classe o limite coperto); 4) scrivere i test JUnit 4; 5) verificare con una analisi per mutazione che i test scoprano errori di confine tipici.

Soluzione

1. Classi di equivalenza

variabile classi tipo
eta <6< 6 non valida
da 66 a 1313 valida (risultato 2)
da 1414 a 1717 valida (4)
da 1818 a 6464 valida (6)
da 6565 a 120120 valida (8)
>120> 120 non valida
premium false valida (nessuna aggiunta)
true valida (+2)

2. Valori limite

I bordi tra le classi sono 66, 1414, 1818, 6565 e 121121 (primi valori della classe a destra) e i loro vicini a sinistra: 5, 6, 13, 14, 17, 18, 64, 65, 120, 121. Si provano i valori sul bordo e accanto; per i due estremi non validi bastano 5 e 121.

3. Tabella dei casi

id età premium atteso copre
T1 5 no IllegalArgumentException non valida bassa, limite 5
T2 121 no IllegalArgumentException non valida alta, limite 121
T3 6 no 2 primo valido, limite 6
T4 13 no 2 ultimo della fascia 6-13
T5 14 no 4 primo della fascia 14-17
T6 17 no 4 ultimo della fascia 14-17
T7 18 no 6 primo della fascia 18-64
T8 64 no 6 ultimo della fascia 18-64
T9 65 no 8 primo della fascia 65+
T10 120 no 8 ultimo valido, limite 120
T11 10 sì 4 premium, fascia 6-13
T12 15 sì 6 premium, fascia 14-17
T13 40 sì 8 premium, fascia 18-64
T14 90 sì 10 premium, fascia 65+

Le due classi non valide sono provate da sole (T1, T2) perché un'eccezione nasconderebbe l'esito di altri controlli. I casi premium = sì usano valori interni delle fasce (i bordi sono già coperti con premium = no), perché l'aggiunta di 2 è indipendente dalla fascia: 4 casi bastano a coprire tutte le fasce senza moltiplicare i test. In tutto 14 test, non 10×2=2010 \times 2 = 20 né 121 (test esaustivo).

4. Codice e test

Il codice sotto test:

java
/**
 * Specifica (R-23): numero massimo di documenti in prestito contemporaneo.
 *  - eta' valida da 6 a 120 anni compresi, altrimenti IllegalArgumentException;
 *  - 6..13 anni: 2 documenti; 14..17: 4; 18..64: 6; 65 in su: 8;
 *  - la tessera premium aggiunge 2 documenti in ogni fascia.
 */
public class LimitePrestiti {
    public static int massimo(int eta, boolean premium) {
        if (eta < 6 || eta > 120) throw new IllegalArgumentException("eta' non valida: " + eta);
        int base;
        if (eta <= 13) base = 2;
        else if (eta <= 17) base = 4;
        else if (eta <= 64) base = 6;
        else base = 8;
        return premium ? base + 2 : base;
    }
}

I test JUnit 4:

java
import static org.junit.Assert.assertEquals;

import org.junit.Test;

public class LimitePrestitiTest {
    // classi non valide: eta' < 6 (limite 5) ed eta' > 120 (limite 121)
    @Test(expected = IllegalArgumentException.class)
    public void sottoIlMinimo() { LimitePrestiti.massimo(5, false); }

    @Test(expected = IllegalArgumentException.class)
    public void sopraIlMassimo() { LimitePrestiti.massimo(121, false); }

    // classe 6..13
    @Test public void primoValido()      { assertEquals(2, LimitePrestiti.massimo(6, false)); }
    @Test public void ultimoBambino()    { assertEquals(2, LimitePrestiti.massimo(13, false)); }

    // classe 14..17
    @Test public void primoRagazzo()     { assertEquals(4, LimitePrestiti.massimo(14, false)); }
    @Test public void ultimoRagazzo()    { assertEquals(4, LimitePrestiti.massimo(17, false)); }

    // classe 18..64
    @Test public void primoAdulto()      { assertEquals(6, LimitePrestiti.massimo(18, false)); }
    @Test public void ultimoAdulto()     { assertEquals(6, LimitePrestiti.massimo(64, false)); }

    // classe 65..120
    @Test public void primoAnziano()     { assertEquals(8, LimitePrestiti.massimo(65, false)); }
    @Test public void ultimoValido()     { assertEquals(8, LimitePrestiti.massimo(120, false)); }

    // seconda variabile (premium): un caso per fascia, scelti ai bordi diversi da quelli sopra
    @Test public void premiumBambino()   { assertEquals(4, LimitePrestiti.massimo(10, true)); }
    @Test public void premiumRagazzo()   { assertEquals(6, LimitePrestiti.massimo(15, true)); }
    @Test public void premiumAdulto()    { assertEquals(8, LimitePrestiti.massimo(40, true)); }
    @Test public void premiumAnziano()   { assertEquals(10, LimitePrestiti.massimo(90, true)); }
}

Esecuzione:

LimitePrestitiTest: eseguiti 14, falliti 0, ignorati 0

I valori attesi vengono dalla specifica, non dal codice: se il codice fosse sbagliato i test lo direbbero.

5. Analisi per mutazione

Per vedere se i test sono buoni si introducono nel codice errori tipici (mutanti) e si controlla che almeno un test fallisca. Esecuzione reale di cinque mutanti, ciascuno con una sola modifica:

mutante modifica test che falliscono
M1 eta <= 17 →\to eta <= 18 primoAdulto (18 dà 4 invece di 6)
M2 eta <= 13 →\to eta < 13 ultimoBambino (13 dà 4 invece di 2)
M3 eta > 120 →\to eta >= 120 ultimoValido (120 lancia l'eccezione)
M4 eta <= 64 →\to eta <= 65 primoAnziano (65 dà 6 invece di 8)
M5 base + 2 →\to base + 1 quattro test sul premium

Tutti e cinque i mutanti sono uccisi, e ogni errore di confine è scoperto dal test sul valore limite corrispondente: con soli valori centrali (10, 40, 90) M1-M4 sarebbero sopravvissuti. Un mutante sopravvissuto indicherebbe un test mancante (o un mutante equivalente, che cambia il codice ma non il comportamento).

Versione ripasso

  • Specifica R-23: età valida 6-120 (altrimenti IllegalArgumentException); 6-13 →\to 2; 14-17 →\to 4; 18-64 →\to 6; 65+ →\to 8; premium +2.
  • Classi per eta: <6< 6 (non valida), 66-1313, 1414-1717, 1818-6464, 6565-120120, >120> 120 (non valida). Per premium: no, sì.
  • Valori limite: 5, 6, 13, 14, 17, 18, 64, 65, 120, 121 (sul bordo e accanto). Le classi non valide da sole. Premium con valori interni (10, 15, 40, 90): l'aggiunta è indipendente dalla fascia. In tutto 14 test.
casi atteso
5, 121 (no premium) eccezione
6, 13 2
14, 17 4
18, 64 6
65, 120 8
10, 15, 40, 90 (premium) 4, 6, 8, 10
java
@Test(expected = IllegalArgumentException.class)
public void sottoIlMinimo() { LimitePrestiti.massimo(5, false); }
@Test public void ultimoBambino() { assertEquals(2, LimitePrestiti.massimo(13, false)); }
@Test public void primoRagazzo()  { assertEquals(4, LimitePrestiti.massimo(14, false)); }
@Test public void premiumAdulto() { assertEquals(8, LimitePrestiti.massimo(40, true)); }
  • Esito: eseguiti 14, falliti 0. Attesi dalla specifica, non dal codice.
  • Mutazione: M1 <= 17 →\to <= 18: fallisce primoAdulto; M2 <= 13 →\to < 13: ultimoBambino; M3 > 120 →\to >= 120: ultimoValido; M4 <= 64 →\to <= 65: primoAnziano; M5 + 2 →\to + 1: quattro test premium. Tutti uccisi; con soli valori centrali M1-M4 sopravvivrebbero. Un mutante sopravvissuto = test mancante o mutante equivalente.
  • Errori tipici: testare solo valori centrali; più classi non valide nello stesso caso; atteso ricavato dal codice; moltiplicare i casi per tutte le combinazioni (20 o 121) invece di scegliere con criterio; dimenticare il limite alto (121).

Esercizi su questo argomento

Teoria collegata