Evitare NullPointerException nei test di integrazione?

0

Durante la scrittura dei test di integrazione per le applicazioni di primavera, tendo a verificare i diversi oggetti di dati multilivello per esempio:

AssertNotNull("Node 10 is null", getNode1().getNode8().getNode10().getNode7());

Queste asserzioni funzionano bene purché gli oggetti A - > B - > C non è nullo, altrimenti se uno di questi è nullo, si finisce con un NPE.

In una prima vista una soluzione plausibile sembra sequenzialmente tutti i nodi genitore

AssertNotNull("Node 1 is null", getNode1());
AssertNotNull("Node 8 is null", getNode1().getNode8());
AssertNotNull("Node 10 is null", getNode1().getNode8().getNode10());
AssertNotNull("Node 7 is null", getNode1().getNode8().getNode10().getNode7());

Ma se comincio a fare per ogni nodo, finirò per avere un mucchio di codice ripetuto ingarbugliato che fluttua intorno.

Poiché non è un codice di sviluppo e vorrei vedere la causa dell'errore e non un NPE non elaborato, specialmente quando ho 100 test di questo tipo che li affermano. Quale sarebbe la migliore pratica in questo caso per evitare NPE o non evitarla del tutto?

    
posta Anirudh 06.06.2018 - 08:53
fonte

4 risposte

2

I test di integrazione sembrano fare affidamento sulla costruzione di alberi di nodi specifici. Da ciò, suppongo che tu stia testando un pezzo di logica che crea l'albero dei nodi appropriato e lo restituisce.

Se è quello che stai facendo, allora è abbastanza ovvio che devi testare la struttura dell'albero del nodo. Quanto più complesse e varie sono le uscite possibili, tanto più devi affermare.

Ma non fa parte dell'approccio "nodo" il fatto che i nodi sono riutilizzabili pezzi di logica?

Ciò non significa quindi che un singolo test dovrebbe solo scavare in profondità uno livello, non più livelli? Stai raggruppando più test nello stesso metodo?

Potrebbe trattarsi di affari specifici. Forse ti stai aspettando alberi a più livelli. Forse questo può essere semplificato in una creazione (e test) livello per livello. È difficile rispondere senza conoscere il contesto specifico.

Se (e solo se) questo approccio multilivello è effettivamente garantito:

AssertNotNull("Node 1 is null", getNode1());
AssertNotNull("Node 8 is null", getNode1().getNode8());
AssertNotNull("Node 10 is null", getNode1().getNode8().getNode10());
AssertNotNull("Node 7 is null", getNode1().getNode8().getNode10().getNode7());

Ti interessano i nodi 1, 8 e 10? O stai semplicemente "passando attraverso i movimenti" per arrivare al nodo 7?

Se l'unica domanda significativa a cui stai rispondendo è "ho ricevuto un nodo 7?", quindi non ha senso affermare gli altri nodi. Se qualcuno di essi è nullo, ciò costringerà la domanda sul nodo 7 a rispondere con "no".

Non ha senso eseguire quattro diversi asserimenti per rispondere a una singola domanda binaria ("ho ricevuto un nodo 7?").

Aggiornamento dal commento pubblicato

Questa era una risposta di commento all'altra risposta.

Ho poca esperienza in Java (sono uno sviluppatore di C #), ma dalle esperienze passate è considerato accettabile provare / catturare una situazione del genere al fine di non verificarne i fatti in ogni fase.

In notazione C # eccessivamente semplificata:

public bool DoesNode7Exist(Node node1)
{
    try
    {
         return n1.getNode8().getNode10().getNode7() != null; 
    }
    catch
    {
         //catches every possible null in the hierarchy
         return false;
    }
}

Si noti che questo è strongmente semplificato per il gusto di esempio. Inoltre, poiché C # ora ha l'operatore ?. , questo approccio è diventato obsoleto; ma non sono sicuro che Java abbia una soluzione simile disponibile al giorno d'oggi.

Aggiornamento dal tuo commento

This question is in fact use case independant, I dont think its useful to explain the whole business model behind these assertions, Question is simple enough, i wouldn't complicate it further.

Poiché il modello è presentato in questa domanda, il drill down nell'albero dei nodi sta violando la metodologia di test prevista. Ogni livello dell'albero deve essere testato individualmente, il che esclude intrinsecamente un problema in cui si esegue il drill-down di più di un singolo livello.

Il problema qui è che stai raggruppando diversi test in un singolo metodo di test; e quindi cercando di evitare di eseguire ogni test. Non funziona così. Devi testarlo tutto e deve essere testato in passaggi separati.

Inoltre, gli alberi basati su nodi si basano sulla logica riutilizzabile . Devi solo testare un singolo nodo (e la sua connessione ai suoi figli) per confermare che i nodi possono essere concatenati indefinitamente. Questo è un ulteriore motivo per cui non è necessario eseguire il drill down di due o più livelli, poiché tutto può essere verificato osservando una relazione figlio / genitore singolo.

    
risposta data 06.06.2018 - 11:34
fonte
1

Bene, suppongo che dipenda. Stai verificando se un nodo particolare è nullo? È quella parte del test? Perché se non lo è, non dovresti testarlo. Fallirà la prima volta che supponi che il nodo non sia nullo ed è.

Quindi, se si prevede che un nodo possa essere nullo? Dovresti testarlo come faresti in qualsiasi programma e agire di conseguenza:

if(getNode1() != null) {
    // Test Node1 ...
}

Come regola generale, direi di no, non dovresti affermare che un nodo non è nullo. L'eccezione a questa regola è solo quando questa è la parte che stai testando:

@Test
public void testNode8Existence() {
    AssertNotNull(getNode1().getNode8());
}
    
risposta data 06.06.2018 - 09:24
fonte
1

Le NullPointerException vanno bene in quanto ti dicono che alcuni presupposti sono infranti che è ciò che vuoi sapere (e junit riporta comunque bene quale è l'aspetto che potresti voler approfondire).

Ciò che TUO anche tu vuoi sapere è quale si è rotto!

In questo caso puoi semplicemente usare il modo standard:

assertNotNull("node8", Objects.requireNonNull(getNode1(), "node 1").getNode8());

o semplicemente attenersi a requireNonNull.

    
risposta data 06.06.2018 - 13:22
fonte
1

I framework dei test unitari hanno almeno questi 3 stati:

  • Successo : tutte le asserzioni sono passate e hanno raggiunto la fine del metodo di test
  • Errore : un'asserzione non è riuscita e un'eccezione non riuscita è stata lanciata
  • Errore - è stata lanciata un'eccezione non prevista (dico imprevisto perché alcuni framework di test unitari consentono di fornire un attributo metodo per verificare che sia stata generata un'eccezione prevista)

Alcuni framework di unit test hanno il concetto di un assunto , dove se l'ipotesi fallisce, il test viene saltato. Quelli sono più utili quando si esegue lo stesso test su un intervallo di valori in cui alcuni test si applicano solo in determinate situazioni. Ciò non è tuttavia pertinente a questa domanda.

Principio generale

Vuoi che sia ovvio cosa stai testando. Ecco perché ci sono buone linee guida per separare il test in Arrange, Act e Assert. In altre parole, imposti i prerequisiti, esegui l'azione che stai realmente testando, quindi asserisci che il risultato è come previsto.

Asserisci le cose importanti

Nei casi in cui esiste una catena di oggetti potenzialmente nullable e c'è solo una combinazione valida, quindi asserisci il modulo finale. Se ricevi un NullPointerException (Java) o NullReferenceException (C #) o qualsiasi equivalente nella tua lingua, si tratta di un errore di test.

Il trade-off è la leggibilità e la specificità. Aggiungendo un'asserzione per ogni fase si aggiunge rumore che può nascondere ciò che si sta tentando di affermare. Ciò che ottieni da questo è che tu sai esattamente dove nella catena hai un nulla. Tuttavia, in catene lunghe come te potresti entrare in un albero degli oggetti che potrebbe non essere così importante.

Questo approccio rende il test reale più visibile:

AssertNotNull("Node 7 is null", getNode1().getNode8().getNode10().getNode7());

// more specific assertions about Node7 where the real test is

Se si ottiene un'eccezione null, si sa che manca qualcosa nella catena. Potrebbe essere necessario attivare il debugger per scoprire esattamente quale dei nodi è nullo, ma ciò non accade spesso dopo aver eseguito il test e la maggior parte degli IDE facilitano il debug dei test.

Semplificazione dell'API

I test come questo possono mostrare la debolezza o la fragilità della tua API. Capisco che gli alberi degli oggetti sono un caso speciale, ma in generale le catene di metodi lunghi come il tuo esempio tendono a essere fragili. Può far sì che i tuoi metodi abbiano un sacco di codice di preambolo per garantire che i dati abbiano il percorso completo per qualsiasi oggetto tu abbia bisogno.

Tutto ciò che puoi fare per accorciare la catena di metodi può solo migliorare l'affidabilità del tuo codice. In altre parole ci sono meno cose che vanno male con le catene short object / method.

Queste domande dovrebbero essere prese seriamente in considerazione insieme ai compromessi che hai per il tuo codice. Potrebbe essere la lunga catena di metodi è davvero l'opzione migliore, o almeno il costo di fissare l'API è troppo da assumere in questo momento.

    
risposta data 06.06.2018 - 15:04
fonte

Leggi altre domande sui tag