È una buona idea disporre di metodi di prova separati per ogni passaggio?


10

Sto testando un API REST. Diciamo che restituisce una struttura JSON. Qual è l'approccio migliore per testare il server? Ogni passaggio del test può avere esito positivo solo se tutti i precedenti hanno avuto esito positivo.

Struttura A: prova tutto in una volta

- Test method 1:
    - make server request
    - assert http response code was 200
    - assert returned file is not empty
    - assert returned file has valid JSON syntax
    - assert returned JSON contains key X

Questa sembra essere la soluzione migliore.

vantaggi:

  • Solo una richiesta del server
  • Sto testando il comportamento nel suo insieme "Il server restituisce un JSON con la chiave X?"

Struttura B: aggiungere gradualmente affermazioni a ciascun test

 - Test method 1:
     - make server request
     - assert http response code was 200
 - Test method 2:
     - make server request
     - assert returned file is not empty
 - Test method 3:
     - make server request
     - assert returned file has valid JSON syntax
 - Test method 4:
     - make server request
     - assert returned JSON contains key X

È così che ho iniziato a farlo ed ero convinto che questo dovrebbe essere il modo migliore perché ogni metodo testa solo una cosa e questo crea una migliore separazione. Ma ora penso che, poiché questi non sono test unitari, la mia separazione non è appropriata e dovrei testare il comportamento nel suo insieme.

Struttura C: effettuare una volta la richiesta ed eseguire metodi di prova separati sulla risposta memorizzata nella cache

- make server request and cache it (allow read-only access)

 - Test method 1:
     - assert http response code was 200 on cached server request
 - Test method 2:
     - assert returned file is not empty on cached server request
 - Test method 3:
     - assert returned file has valid JSON syntax on cached server request
 - Test method 4:
     - assert returned JSON contains key X on cached server request

vantaggi:

  • Nessuna richiesta di server ripetuta (costosa)
  • Ha ancora metodi di prova con asserzione singola

Qual è la struttura di test più sensata da usare?


Smetti di cambiare la tua domanda in un secondo momento in modo da invalidare le risposte esistenti! Grazie.
Doc Brown,

Ci scusiamo per l'inconveniente, ma proporresti di fare diversamente?
MrPlow,

Innanzitutto, pensaci due volte se hai davvero bisogno di cambiare la tua domanda in questo modo. Se pensi davvero di dover aggiungere qualcosa che invalida alcune risposte, puoi informare tutti gli autori di tali risposte lasciando un commento sotto la loro risposta chiedendo loro se vogliono cambiare o aggiungere qualcosa nel loro testo.
Doc Brown,

2
In realtà ho ipotizzato che gli autori delle risposte SIANO informati se la domanda viene modificata. Questo è il motivo per cui non volevo inviare messaggi di spam con dichiarazioni off topic. Informerò gli autori in futuro. E grazie per aver fornito una risposta alla mia domanda.
MrPlow,

Risposte:


3

Le migliori pratiche hanno sempre uno scopo, un motivo dietro di loro. È sempre una buona idea considerare questi motivi nel tuo progetto, specialmente quando stai cercando di decidere come e quanto difficile seguire queste migliori pratiche.

In questo caso, il ragionamento principale alla base del rendere ogni test una cosa sola è che se la prima cosa fallisce, la seconda non verrà testata. Dal momento che troppi opinionisti sembrano trovare il merito di spezzare tutto nei pezzetti più piccoli possibili e di avvolgerli il più possibile in eccesso, ciò ha dato vita all'idea che ogni test dovrebbe contenere una singola affermazione.

Non seguirlo ciecamente. Anche se ogni test dovrebbe testare una cosa, dovresti comunque pensare a decidere quanto grande o piccola dovrebbe essere ogni "cosa", e per farlo devi tenere presente il motivo per cui vuoi che ogni test test una cosa - per assicurarti un bug nella prima cosa non sta lasciando la seconda cosa non testata.

Quindi, devi chiederti: "Ho davvero bisogno di questa garanzia qui?"

Diciamo che c'è un bug nel primo caso di test - il codice di risposta HTTP no 200. Quindi inizi a hackerare il codice, scopri perché non hai ricevuto il codice di risposta che dovresti avere e risolvi il problema. E adesso?

  • Se si esegue nuovamente il test manualmente, per verificare che la correzione abbia risolto il problema, è necessario eseguire qualsiasi altro problema nascosto dal primo errore.
  • Se non lo esegui manualmente (forse perché impiega troppo tempo?) E spingi semplicemente la correzione in attesa che il server dei test automatizzati esegua tutto, allora potresti voler inserire assert diversi in test diversi. I cicli in questo caso sono molto lunghi, quindi vale la pena fare lo sforzo di scoprire quanti più bug in ogni ciclo.

Ci sono alcune altre cose da considerare:

Dipendenze delle asserzioni

So che i test che hai descritto sono solo un esempio, e i tuoi test reali sono probabilmente più complicati, quindi quello che sto per dire potrebbe non essere valido con la stessa forza nei test reali, ma potrebbe comunque essere in qualche modo efficace potrebbe voler considerarlo.

Se si dispone di un servizio REST (o di qualsiasi altro protocollo HTTP) che restituisce risposte in formato JSON, di solito si scrive una semplice classe client che consente di utilizzare i metodi REST come i normali metodi che restituiscono oggetti regolari. Supponendo che il cliente abbia test separati per assicurarsi che funzioni, avrei abbandonato le prime 3 affermazioni e ne avrei conservate solo 4!

Perché?

  • La prima asserzione è ridondante: la classe client dovrebbe generare un'eccezione se il codice di risposta HTTP non è 200.
  • La seconda asserzione è ridondante: se la risposta è vuota, l'oggetto risultato sarà nullo o un'altra rappresentazione di un oggetto vuoto e non avrai nessun posto dove mettere la chiave X.
  • La terza asserzione è ridondante: se JSON non è valido, si otterrebbe un'eccezione quando si tenta di analizzarla.

Quindi non è necessario eseguire tutti questi test: basta eseguire il quarto test e se si verifica uno dei bug che i primi tre tentativi di rilevare si verificano, il test fallirà con un'eccezione adeguata prima ancora di ottenere l'asserzione effettiva.

Come si desidera ricevere i rapporti?

Supponiamo che non riceviate e-mail da un server di prova, ma invece il dipartimento di controllo qualità esegue i test e avvisa l'utente dei test non riusciti.

Jack del QA bussa alla tua porta. Dice che il primo metodo di test non è riuscito e che il metodo REST ha restituito un codice di risposta errato. Lo ringrazi e inizi a cercare la causa principale.

Quindi arriva Jen dal QA e afferma che il terzo metodo di test ha avuto esito negativo: il metodo REST non ha restituito un JSON valido nel corpo della risposta. Le dici che stai già esaminando quel metodo e ritieni che la stessa cosa che ha causato la restituzione di un codice di uscita errato abbia causato anche la restituzione di qualcosa che non è un JSON valido e assomiglia più a una traccia dello stack delle eccezioni.

Torna a lavorare, ma poi arriva Jim dal QA, dicendo che il quarto metodo di test ha fallito e non c'è la chiave X nella risposta ...

Non puoi nemmeno cercare il motivo perché è difficile guardare il codice quando non hai uno schermo di computer. Se Jim fosse stato abbastanza veloce avrebbe potuto schivare in tempo ...

Le e-mail dal server di test sono più facili da eliminare, ma comunque - non preferiresti essere avvisato UNA VOLTA che qualcosa non va con il metodo di test e guardare tu stesso i log di test pertinenti?


3

Se puoi tranquillamente supporre che effettuare una richiesta del server con gli stessi parametri si comporterà sempre lo stesso, il metodo B è quasi inutile: perché dovresti chiamare quattro volte lo stesso metodo per ottenere gli stessi dati di risposta quattro volte quando una chiamata è sufficiente?

E se non puoi assumere questo in modo sicuro e vuoi renderlo parte del test, potresti essere meglio eseguire il test A più volte.

L'unica situazione ipotetica in cui vedo dove B potrebbe avere un vantaggio è quando il tuo framework di test consente di attivare e disattivare solo metodi di test espliciti e ti aspetti che sia necessario farlo per le singole fasi del test.

L'alternativa C sembra combinare A con l'unico vantaggio che ho menzionato sopra per B. Se il framework di test consente di strutturare facilmente il codice in questo modo, senza sovraccarico sopra B, questo è un approccio fattibile. Tuttavia, questo aggiunge ulteriore complessità ad A, quindi lo userei solo se volessi accendere e spegnere i singoli test, altrimenti applicare il principio YAGNI e attenermi alla soluzione più semplice (A).

TLDR: iniziare con A se si è sicuri di voler sempre eseguire tutti gli assert in un test, fare riferimento a C se si nota che è necessario avere un controllo più semplice dall'esterno sui singoli assert.


0

Come qualsiasi codice, evita l'ottimizzazione prematura. Prima scrivi i tuoi test in modo che siano semplici da leggere e da mantenere. Quando i test iniziano a rallentare, ottimizzali. Nel tuo esempio abbastanza semplice, A e B saranno entrambi facili da leggere e mantenere, quindi scegli quello che vuoi finché le cose non diventano troppo lente (struttura B) o troppo complicate (struttura A).

Se il tuo server è senza stato, allora puoi ottimizzare confrontando la risposta effettiva con una risposta prevista per controllare l'intero messaggio in una volta sola. Ovviamente ciò sarà a spese della leggibilità.

Se il tuo server è statefull ed è necessario effettuare più chiamate API lente per portare il server in uno stato per il test, allora dovrai adottare un approccio diverso o l'esecuzione dei test potrebbe richiedere alcuni minuti. Ad esempio, è possibile eseguire un aggiornamento del database per iniettare i dati in un database di test in modo da poter ottenere rapidamente un oggetto in uno stato appropriato per il test. Il test è rapido e leggibile ma più difficile da mantenere. In alternativa, potresti essere in grado di scrivere una facciata di fronte all'api, quindi più chiamate API diventano un'unica chiamata API che si avvicina maggiormente al processo aziendale che stai testando.


0

I test non devono condividere elementi: a partire da zero si evita l'influenza di un test su un altro. Questo ti rende anche in grado di eseguire test in ordine casuale.
Quindi il modo C non dovrebbe essere accettato.


Quando scrivi un codice (o forse addirittura crea qualcos'altro), chiediti sempre: "perché esiste una tale pratica?"
Perché diciamo che dovrebbero esserci diversi test per tutto?

Esistono due casi in cui è necessario:

  1. quando non puoi fare affidamento sul "ogni passaggio del test può avere successo solo se tutti i precedenti hanno avuto successo"
  2. quando i test non hanno messaggi di asserzione descrittivi

Esistono due motivi per cui si affrontano questi casi:

  1. "ogni passaggio del test può avere esito positivo solo se tutti i precedenti hanno avuto esito positivo" non è realmente applicabile alla funzionalità del prodotto
  2. non hai abbastanza conoscenza del prodotto a causa della mancanza di esperienza o di tempo, o della complessità schiacciante del prodotto

Se per qualche motivo non è possibile dichiarare almeno uno di questi motivi per avere luogo solo prendere ciecamente la struttura B .


In caso contrario (spero che si ottiene qui) si sceglie A .


Inoltre, è possibile porre questa domanda sul sito Stackexchange di Software Quality Assurance & Testing .

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.