Ha senso misurare la copertura condizionale per il codice Java 8?


19

Mi chiedo se la misurazione della copertura del codice condizionale da parte degli strumenti attuali per Java non sia obsoleta da quando Java 8 è arrivato. Con Java 8 Optionale Streamspesso possiamo evitare rami / loop di codice, il che rende facile ottenere una copertura condizionale molto elevata senza testare tutti i possibili percorsi di esecuzione. Confrontiamo il vecchio codice Java con il codice Java 8:

Prima di Java 8:

public String getName(User user) {
    if (user != null) {
        if (user.getName() != null) {
            return user.getName();
        }
    }
    return "unknown";
}

Esistono 3 possibili percorsi di esecuzione nel metodo sopra. Per ottenere il 100% della copertura condizionale è necessario creare 3 test unitari.

Java 8:

public String getName(User user) {
    return Optional.ofNullable(user)
                   .map(User::getName)
                   .orElse("unknown");
}

In questo caso, i rami sono nascosti e abbiamo solo 1 test per ottenere una copertura del 100% e non importa in quale caso testeremo. Sebbene ci siano ancora gli stessi 3 rami logici che dovrebbero essere coperti, credo. Penso che al giorno d'oggi renda completamente inaffidabili le statistiche sulla copertura condizionale.

Ha senso misurare la copertura condizionale per il codice Java 8? Esistono altri strumenti per individuare il codice non testato?


5
Le metriche di copertura non sono mai state un buon modo per determinare se il codice è ben testato, ma solo un modo per determinare ciò che non è stato testato. Un buon sviluppatore penserà attraverso i vari casi nella sua mente e escogiterà test per tutti loro - o almeno per tutto ciò che ritiene importanti.
kdgregory,

3
Ovviamente un'elevata copertura condizionale non significa che abbiamo buoni test, ma penso che sia ENORME vantaggio sapere quali percorsi di esecuzione sono scoperti e questo è principalmente il problema. Senza copertura condizionale è molto più difficile individuare scenari non testati. Per quanto riguarda i percorsi: [user: null], [user: notnull, user.name:null], [user: notnull, user.name:notnull]. Cosa mi manca
Karol Lewandowski,

6
Qual è il contratto di getName? Sembra che se userè nullo, dovrebbe restituire "sconosciuto". Se usernon è nullo ed user.getName()è nullo, dovrebbe restituire "sconosciuto". Se usernon è nullo e user.getName()non è nullo, dovrebbe restituirlo. Quindi testereste questi tre casi perché è questo il contratto getName. Sembra che tu lo stia facendo all'indietro. Non vuoi vedere le filiali e scrivere i test in base a quelli, vuoi scrivere i test in base al tuo contratto e assicurarti che il contratto sia completato. Questo è quando hai una buona copertura.
Vincent Savard,

1
Ancora una volta, non sto dicendo che la copertura dimostri che il mio codice è stato testato perfettamente, ma è stato uno strumento MOLTO prezioso che mi ha mostrato ciò che non ho testato di sicuro. Penso che i contratti di prova siano inseparabili dai percorsi di esecuzione dei test (il tuo esempio è eccezionale in quanto implica un meccanismo linguistico implicito). Se il percorso non è stato testato, il contratto non è stato completamente testato o il contratto non è completamente definito.
Karol Lewandowski,

2
Ripeterò il mio punto precedente: è sempre così, a meno che non ti limiti solo alle caratteristiche del linguaggio di base e non chiami mai alcuna funzione che non sia stata strumentata. Ciò significa che non esistono librerie di terze parti e nessun utilizzo dell'SDK.
kdgregory,

Risposte:


4

Esistono strumenti che misurano i rami logici che possono essere creati in Java 8?

Non ne sono a conoscenza. Ho provato ad eseguire il codice che hai tramite JaCoCo (aka EclEmma) solo per essere sicuro, ma mostra 0 rami nella Optionalversione. Non conosco alcun metodo per configurarlo per dire diversamente. Se lo configurassi per includere anche i file JDK, teoricamente mostrerebbe i rami in Optional, ma penso che sarebbe sciocco iniziare a verificare il codice JDK. Devi solo supporre che sia corretto.

Penso che il problema principale, tuttavia, sia rendersi conto che i rami aggiuntivi che avevi prima di Java 8 erano, in un certo senso, rami creati artificialmente. Il fatto che non esistano più in Java 8 significa solo che ora hai lo strumento giusto per il lavoro (in questo caso Optional). Nel codice pre-Java 8 dovevi scrivere test di unità extra in modo da poter avere la certezza che ogni ramo di codice si comporta in modo accettabile, e questo diventa un po 'più importante in sezioni di codice che non sono banali come il User/ getNameesempio.

Nel codice Java 8, stai invece confidando nel JDK che il codice funziona correttamente. Così Optionalcom'è , dovresti trattare quella linea proprio come lo trattano gli strumenti di copertura del codice: 3 linee con 0 rami. Che ci siano altre linee e rami nel codice qui sotto è qualcosa a cui non hai prestato attenzione prima, ma è esistito ogni volta che hai usato qualcosa come un ArrayListo HashMap.


2
"Che non esistono più in Java 8 ..." - Non posso essere d'accordo, Java 8 è retrocompatibile ife nullfa ancora parte del linguaggio ;-) È ancora possibile scrivere codice alla vecchia maniera e passare nullutente o utente con nullnome. I tuoi test dovrebbero solo dimostrare che il contratto è rispettato indipendentemente da come viene implementato il metodo. Il punto è che non esiste uno strumento per dirti se hai completamente testato il contratto.
Karol Lewandowski,

1
@KarolLewandowski Penso che Shaz stia dicendo che se ti fidi del modo in cui Optional(e dei relativi metodi) funzionano, non devi più testarli. Non nello stesso modo in cui hai testato un if-else: ogni ifera un potenziale campo minato. Optionale simili idiomi funzionali sono già codificati e garantiti per non farti inciampare, quindi essenzialmente c'è un "ramo" che è svanito.
Andres F.

1
@AndresF. Non credo che Karol stia suggerendo di testare Optional. Come ha detto, logicamente dovremmo ancora testare che getName()gestisce vari input possibili nel modo in cui intendiamo, indipendentemente dalla sua implementazione. È più difficile determinarlo senza che gli strumenti di copertura del codice aiutino nel modo in cui sarebbe pre-JDK8.
Mike Partridge,

1
@MikePartridge Sì, ma il punto è che ciò non avviene tramite la copertura delle filiali. La copertura del ramo è necessaria quando si scrive if-elseperché ognuno di questi costrutti è completamente ad-hoc. Al contrario, Optional, orElse, map, ecc, sono tutti già testati. I rami, in effetti, "svaniscono" quando usi modi di dire più potenti.
Andres F.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.