Come si scrivono i test unitari per il codice con risultati difficili da prevedere?

122

Lavoro spesso con programmi numerici / matematici, in cui è difficile prevedere in anticipo il risultato esatto di una funzione.

Nel tentativo di applicare TDD con questo tipo di codice, spesso trovo che scrivere il codice sotto test sia molto più semplice rispetto alla scrittura di unit test per quel codice, perché l'unico modo per sapere il risultato atteso è applicare l'algoritmo stesso ( se nella mia testa, sulla carta o dal computer). Questo sembra sbagliato, perché sto usando efficacemente il codice sotto test per verificare i miei test unitari, invece del contrario.

Esistono tecniche note per scrivere test di unità e applicare TDD quando il risultato del codice in esame è difficile da prevedere?

Un esempio (reale) di codice con risultati difficili da prevedere:

Una funzione weightedTasksOnTime che, data una quantità di lavoro al giorno workPerDay nell'intervallo (0, 24), il tempo corrente initialTime > 0 e un elenco di compiti taskArray ; ciascuno con un tempo per completare la proprietà time > 0, la data di scadenza due e il valore di importanza importance ; restituisce un valore normalizzato nell'intervallo [0, 1] che rappresenta l'importanza delle attività che possono essere completate prima del loro due data se ogni attività è completata nell'ordine dato da taskArray , a partire da initialTime .

L'algoritmo per implementare questa funzione è relativamente semplice: iterare su attività in taskArray . Per ogni attività, aggiungi time a initialTime . Se la nuova ora < due , aggiungi importance a un accumulatore. Il tempo viene regolato da workPerDay inverso. Prima di restituire l'accumulatore, dividi per somma delle operazioni da importare per normalizzare.

function weightedTasksOnTime(workPerDay, initialTime, taskArray) {
    let simulatedTime = initialTime
    let accumulator = 0;
    for (task in taskArray) {
        simulatedTime += task.time * (24 / workPerDay)
        if (simulatedTime < task.due) {
            accumulator += task.importance
        }
    }
    return accumulator / totalImportance(taskArray)
}

Credo che il problema di cui sopra possa essere semplificato, pur mantenendo il suo nucleo, rimuovendo workPerDay e il requisito di normalizzazione, per dare:

function weightedTasksOnTime(initialTime, taskArray) {
    let simulatedTime = initialTime
    let accumulator = 0;
    for (task in taskArray) {
        simulatedTime += task.time
        if (simulatedTime < task.due) {
            accumulator += task.importance
        }
    }
    return accumulator
}

Questa domanda riguarda situazioni in cui il codice in prova non è una reimplementazione di un algoritmo esistente. Se il codice è una reimplementazione, è intrinsecamente facile prevedere i risultati, poiché le implementazioni attendibili esistenti dell'algoritmo fungono da oracolo di prova naturale.

    
posta PaintingInAir 08.10.2018 - 00:06
fonte

15 risposte

250

Ci sono due cose che puoi testare nel codice difficile da testare. In primo luogo, i casi degenerati. Cosa succede se non ci sono elementi nella serie di attività, o solo uno o due, ma uno ha superato la data di scadenza, ecc. Tutto ciò che è più semplice del tuo vero problema, ma comunque ragionevole da calcolare manualmente.

Il secondo è i controlli di integrità. Questi sono i controlli che fai dove non sai se una risposta è giusta , ma sicuramente sapresti se è sbagliato . Queste sono le cose come il tempo deve andare avanti, i valori devono essere in un intervallo ragionevole, le percentuali devono aggiungere fino a 100, ecc.

Sì, non è buono come un test completo, ma ti sorprenderebbe quanto spesso incasini i controlli di integrità e degeneri i casi, il che rivela un problema nell'algoritmo completo.

    
risposta data 08.10.2018 - 05:08
fonte
80

Ero solito scrivere test per software scientifico con output difficili da prevedere. Abbiamo fatto un sacco di uso delle relazioni metamorfiche. In sostanza, ci sono cose che sai su come il tuo software dovrebbe comportarsi anche se non conosci esatti output numerici.

Un possibile esempio per il tuo caso: se diminuisci la quantità di lavoro che puoi fare ogni giorno, allora la quantità totale di lavoro che puoi fare sarà nel migliore dei casi lo stesso, ma probabilmente diminuirà. Quindi, esegui la funzione per un numero di valori di workPerDay e assicurati che la relazione sia valida.

    
risposta data 08.10.2018 - 14:03
fonte
38

Le altre risposte hanno buone idee per lo sviluppo di test per edge o error case. Per gli altri, l'utilizzo dell'algoritmo stesso non è l'ideale (ovviamente), ma è comunque utile.

Rileverà se l'algoritmo (oi dati da cui dipende) è cambiato

Se la modifica è un incidente, puoi ripristinare un commit. Se il cambiamento è stato intenzionale, è necessario rivisitare il test dell'unità.

    
risposta data 08.10.2018 - 05:26
fonte
19

Allo stesso modo in cui scrivi i test unitari per qualsiasi altro tipo di codice:

  1. Trova alcuni casi di test rappresentativi e prova quelli.
  2. Trova casi limite e verifica quelli.
  3. Trova le condizioni di errore e prova quelle.

A meno che il tuo codice non contenga elementi casuali o non sia deterministico (cioè non produrrà lo stesso output dato lo stesso input), è un'unità testabile.

Evita gli effetti collaterali o le funzioni che sono influenzate da forze esterne. Le funzioni pure sono più facili da testare.

    
risposta data 08.10.2018 - 00:24
fonte
17

Aggiornamento a causa di commenti postati

La risposta originale è stata rimossa per brevità: puoi trovarla nella cronologia delle modifiche.

PaintingInAir For context: as an entrepreneur and academic, most of the algorithms I design are not requested by anyone other than myself. The example given in the question is part of a derivative-free optimizer to maximize the quality of an ordering of tasks. In terms of how I described the need for the example function internally: "I need an objective function to maximize the importance of tasks that are completed on time". However, there still seems to be a large gap between this request and the implementation of unit tests.

Innanzitutto, un TL; DR per evitare una risposta altrimenti lunga:

Think of it this way:
A customer enters McDonald's, and asks for a burger with lettuce, tomato and hand soap as toppings. This order is given to the cook, who makes the burger exactly as requested. The customer receives this burger, eats it, and then complains to the cook that this is not a tasty burger!

This is not the cook's fault - he's only doing what the customer explicitly asked. It's not the cook's job to check if the requested order is actually tasty. The cook simply creates that which the customer orders. It's the customer's responsibility of ordering something that they find tasty.

Similarly, it's not the developer's job to question the correctness of the algorithm. Their only job is to implement the algorithm as requested.
Unit testing is a developer's tool. It confirms that the burger matches the order (before it leaves the kitchen). It does not (and should not) try to confirm that the ordered burger is actually tasty.

Even if you are both the customer and the cook, there is still a meaningful distinction between:

  • I did not prepare this meal properly, it was not tasty (= cook error). A burnt steak is never going to taste good, even if you like steak.
  • I prepared the meal properly, but I don't like it (= customer error). If you don't like steak, you'll never like eating steak, even if you cooked it to perfection.

Il problema principale qui è che non stai facendo una separazione tra il cliente e lo sviluppatore (e l'analista, anche se tale ruolo può essere rappresentato anche da uno sviluppatore).

Devi distinguere tra testare il codice e testare i requisiti aziendali.

Ad esempio, il cliente vuole che funzioni come [questo] . Tuttavia, lo sviluppatore fraintende e scrive codice che [che] .

Lo sviluppatore scriverà quindi dei test unitari che testano se [that] funziona come previsto. Se ha sviluppato l'applicazione correttamente, i suoi test di unità passeranno anche se l'applicazione non esegue [questo] , cosa che il cliente si aspettava.

Se desideri testare le aspettative del cliente (i requisiti aziendali), è necessario farlo in un passaggio separato (e successivo).

Un semplice flusso di lavoro di sviluppo per mostrare quando eseguire questi test:

  • Il cliente spiega il problema che desidera risolvere.
  • L'analista (o sviluppatore) scrive questo in un'analisi.
  • Lo sviluppatore scrive il codice che fa ciò che descrive l'analisi.
  • Lo sviluppatore verifica il suo codice (test di unità) per verificare se ha seguito correttamente l'analisi
  • Se l'unità test fallisce, lo sviluppatore torna allo sviluppo. Questo ciclo si interrompe indefinitamente, finché l'unità verifica tutti i passaggi.
  • Ora con una base di codice testata (confermata e passata), lo sviluppatore costruisce l'applicazione.
  • L'applicazione viene fornita al cliente.
  • Il cliente ora verifica se l'applicazione che gli viene fornita risolve effettivamente il problema che ha cercato di risolvere (test del QA) .

Ci si potrebbe chiedere quale sia il motivo di fare due test separati quando il cliente e lo sviluppatore sono la stessa cosa. Dato che non ci sono "hand off" dallo sviluppatore al cliente, i test vengono eseguiti uno dopo l'altro, ma sono ancora passaggi separati.

  • I test unitari sono uno strumento specializzato che ti aiuta a verificare se il tuo stadio di sviluppo è finito.
  • I test del QA vengono eseguiti da utilizzando l'applicazione .

Se vuoi verificare se il tuo algoritmo è corretto, non fa parte del lavoro dello sviluppatore . Questa è la preoccupazione del cliente e il cliente lo testerà utilizzando l'applicazione.

Come imprenditore e accademico, potresti perdere un'importante distinzione qui, che mette in luce le diverse responsabilità.

  • Se l'applicazione non rispetta ciò che il cliente aveva inizialmente chiesto, le successive modifiche al codice vengono in genere eseguite gratuitamente ; poiché si tratta di un errore dello sviluppatore. Lo sviluppatore ha commesso un errore e deve pagare il costo per rettificarlo.
  • Se l'applicazione fa ciò che il cliente aveva inizialmente chiesto, ma il cliente ora ha cambiato idea (ad esempio hai deciso di utilizzare un algoritmo diverso e migliore), le modifiche al codice base sono addebitate al cliente , dal momento che non è colpa dello sviluppatore che il cliente abbia richiesto qualcosa di diverso da quello che desidera ora. È responsabilità del cliente (costi) cambiare idea e quindi gli sviluppatori dedicano maggiori sforzi allo sviluppo di qualcosa che non era stato precedentemente concordato.
risposta data 08.10.2018 - 08:13
fonte
8

Test delle proprietà

A volte le funzioni matematiche sono meglio servite da "Property Testing" piuttosto che da tradizionali test unitari basati su esempi. Ad esempio, immagina di scrivere dei test unitari per qualcosa come una funzione "moltiplicazione" intera. Mentre la funzione stessa può sembrare molto semplice, se è l'unico modo per moltiplicare, come si fa a testarlo a fondo senza la logica nella funzione stessa? Potresti usare tabelle giganti con ingressi / uscite previsti, ma questo è limitato e soggetto a errori.

In questi casi, è possibile verificare le proprietà note della funzione, anziché cercare specifici risultati previsti. Per la moltiplicazione, potresti sapere che moltiplicando un numero negativo e un numero positivo dovrebbe risultare un numero negativo, e che moltiplicando due numeri negativi dovrebbe risultare un numero positivo, ecc. Usando valori randomizzati e poi controllando che queste proprietà siano conservate per tutti i valori di test sono un buon modo per testare tali funzioni. In genere è necessario testare più di una proprietà, ma spesso è possibile identificare un insieme finito di proprietà che convalidano insieme il comportamento corretto di una funzione senza necessariamente conoscere il risultato previsto per ogni caso.

Una delle migliori introduzioni ai Test di proprietà che ho visto è questo in F #. Speriamo che la sintassi non sia un ostacolo alla comprensione della spiegazione della tecnica.

    
risposta data 09.10.2018 - 18:53
fonte
4

Si è tentati di scrivere il codice e poi vedere se il risultato "sembra giusto", ma, come giustamente intuisci, non è una buona idea.

Quando l'algoritmo è difficile puoi fare una serie di cose per rendere più semplice il calcolo manuale del risultato.

  1. Usa Excel. Configura un foglio di calcolo che ti fa alcuni o tutti i calcoli. Rendilo abbastanza semplice in modo che tu possa vedere i passaggi.

  2. Suddividi il tuo metodo in metodi testabili più piccoli, ciascuno con i propri test. Quando sei sicuro che le parti più piccole funzionino, usale per eseguire manualmente il passaggio successivo.

  3. Utilizza le proprietà aggregate per verificare l'integrità. Ad esempio, supponiamo di avere un calcolatore di probabilità; potresti non sapere quali dovrebbero essere i singoli risultati, ma sai che tutti devono sommare al 100%.

  4. Forza bruta. Scrivi un programma che generi tutti i risultati possibili e verifica che nessuno sia migliore di quello che genera l'algoritmo.

risposta data 08.10.2018 - 04:47
fonte
2

TL; DR

Vai alla sezione "test comparativi" per consigli che non sono in altre risposte.

Beginnings

Inizia testando i casi che dovrebbero essere rifiutati dall'algoritmo (ad esempio workPerDay zero o negativo) e i casi che sono banali (ad esempio tasks array vuoto).

Dopodiché, vuoi prima testare i casi più semplici. Per l'input tasks , dobbiamo testare diverse lunghezze; dovrebbe essere sufficiente per testare gli elementi 0, 1 e 2 (2 appartiene alla categoria "molti" per questo test).

Se riesci a trovare input che possono essere calcolati mentalmente, è un buon inizio. Una tecnica che a volte utilizzo è quella di partire da un risultato desiderato e tornare indietro (nelle specifiche) agli input che dovrebbero produrre quel risultato.

Test comparativo

A volte la relazione tra l'output e l'input non è ovvia, ma hai una relazione prevedibile tra diversi output quando viene modificato un input. Se ho capito correttamente l'esempio, quindi aggiungere un'attività (senza modificare altri input) non aumenterà mai la percentuale di lavoro fatto in tempo, quindi possiamo creare un test che chiama la funzione due volte - una volta con e una volta senza l'operazione extra - e afferma la disuguaglianza tra i due risultati.

fallback

A volte ho dovuto ricorrere a un lungo commento che mostra un risultato calcolato a mano in passaggi corrispondenti alla specifica (un commento di questo genere è solitamente più lungo del caso di test). Il caso peggiore è quando è necessario mantenere la compatibilità con un'implementazione precedente in una lingua diversa o per un ambiente diverso. A volte devi solo etichettare i dati del test con qualcosa come /* derived from v2.6 implementation on ARM system */ . Non è molto soddisfacente, ma può essere accettabile come test di fedeltà durante il porting o come stampella a breve termine.

Promemoria

L'attributo più importante di un test è la sua leggibilità - se gli input e gli output sono opachi per il lettore, allora il test ha un valore molto basso, ma se il lettore è aiutato a capire le relazioni tra loro, allora il test serve due scopi.

Non dimenticare di usare "approssimativamente uguali" appropriati per risultati inesatti (ad esempio virgola mobile).

Evita i test eccessivi: aggiungi un test solo se copre qualcosa (come un valore limite) che non viene raggiunto da altri test.

    
risposta data 09.10.2018 - 10:23
fonte
2

Non c'è nulla di molto speciale in questo tipo di funzione difficile da testare. Lo stesso vale per il codice che utilizza interfacce esterne (ad esempio, un'API REST di un'applicazione di terze parti che non è sotto il tuo controllo e che non è certamente da testare con la tua suite di test o utilizza una libreria di terze parti in cui non sei sicuro del formato byte esatto dei valori di ritorno).

È un approccio abbastanza valido per eseguire semplicemente l'algoritmo per alcuni input sensati, vedere che cosa fa, assicurarsi che il risultato sia corretto e incapsulare l'input e il risultato come un caso di test. Puoi farlo per alcuni casi e ottenere così diversi campioni. Cerca di rendere i parametri di input il più diversi possibile. Nel caso di una chiamata API esterna, dovresti fare alcune chiamate contro il sistema reale, tracciarle con qualche strumento e poi prenderle in giro per i tuoi test unitari per vedere come reagisce il tuo programma - che è lo stesso che sceglierne solo alcuni esegue il codice di pianificazione delle attività, verificandole a mano e quindi codifica il risultato nei test.

Quindi, ovviamente, inserisci casi limite come (nel tuo esempio) una lista vuota di compiti; cose del genere.

La tua suite di test forse non sarà eccezionale come per un metodo in cui puoi facilmente prevedere i risultati; ma ancora meglio del 100% rispetto a nessuna suite di test (o solo un test del fumo).

Se il tuo problema, però, è che trovi difficile decidere se un risultato è corretto, allora questo è un problema completamente diverso. Ad esempio, supponiamo di avere un metodo che rileva se un numero arbitrariamente grande è primo. A malapena puoi lanciare un numero casuale e poi "guardare" se il risultato è corretto (supponendo che tu non possa decidere l'eccellenza nella tua testa o su un pezzo di carta). In questo caso, c'è davvero poco che puoi fare - avresti bisogno di ottenere risultati noti (es. Alcuni grandi numeri primi), o implementare la funzionalità con un algoritmo diverso (forse anche una squadra diversa - la NASA sembra essere affezionata a che) e sperare che se l'implementazione è bacata, almeno il bug non porta agli stessi risultati sbagliati.

Se questo è un caso normale per te, allora devi avere una buona conversazione con i tuoi ingegneri dei requisiti. Se non riescono a formulare le tue esigenze in un modo facile  (o del tutto possibile) per controllarti, poi quando sai se hai finito?

    
risposta data 09.10.2018 - 17:52
fonte
1

Incorporare test di asserzione nella suite di test delle unità per test basati su proprietà del proprio algoritmo. Oltre a scrivere test unitari che controllano l'output specifico, scrivere test progettati per fallire attivando errori di asserzione nel codice principale.

Molti algoritmi si basano sulle loro prove di correttezza sul mantenimento di determinate proprietà in tutte le fasi dell'algoritmo. Se è possibile controllare sensibilmente queste proprietà osservando l'output di una funzione, il test dell'unità da solo è sufficiente per testare le proprietà. In caso contrario, i test basati su asserzioni consentono di verificare che un'implementazione mantenga una proprietà ogni volta che l'algoritmo lo assume.

I test basati sull'asserzione espongono difetti dell'algoritmo, bug di codifica e errori di implementazione dovuti a problemi come l'instabilità numerica. Molti linguaggi hanno meccanismi di strip assertion in fase di compilazione o prima che il codice venga interpretato in modo tale che quando vengono eseguiti in modalità di produzione le asserzioni non comportino una penalizzazione delle prestazioni. Se il tuo codice supera i test unitari ma fallisce in un caso reale, puoi riattivare le asserzioni come strumento di debug.

    
risposta data 10.10.2018 - 20:56
fonte
1

Alcune delle altre risposte qui sono molto buone:

  • Test della base, del bordo e dei casi d'angolo
  • Esegui controlli di integrità
  • Esegui test comparativi

... aggiungerei qualche altra tattica:

  • Decomposizione del problema.
  • Dimostra l'algoritmo al di fuori del codice.
  • Verifica che l'algoritmo [testato esternamente] sia implementato come progettato.

La decomposizione consente di garantire che i componenti dell'algoritmo eseguano ciò che si aspetta che facciano. E una "buona" decomposizione ti consente anche di essere incollati correttamente. Una scomposizione grande generalizza e semplifica l'algoritmo nella misura in cui può prevedere a mano i risultati (degli algoritmi generici semplificati) abbastanza bene da scrivere test approfonditi .

Se non riesci a decomporsi fino a quel punto, prova l'algoritmo al di fuori del codice con qualsiasi mezzo sia sufficiente per soddisfare te, i tuoi colleghi, le parti interessate e i clienti. E poi, basta decomporsi abbastanza per dimostrare che la tua implementazione corrisponde al design.

    
risposta data 10.10.2018 - 21:59
fonte
1

Altre risposte sono buone, quindi proverò a colpire alcuni punti che fino ad ora mancano collettivamente.

Ho scritto (e accuratamente testato) il software per l'elaborazione delle immagini usando Synthetic Aperture Radar (SAR). È di natura scientifica / numerica (ci sono un sacco di geometria, fisica e matematica coinvolte).

Un paio di suggerimenti (per test scientifici / numerici generali):

1) Utilizza gli invers. Qual è il fft di [1,2,3,4,5] ? Nessuna idea. Cos'è ifft(fft([1,2,3,4,5])) ? Dovrebbe essere [1,2,3,4,5] (o vicino ad esso potrebbero comparire errori in virgola mobile). Lo stesso vale per il caso 2D.

2) Usa affermazioni conosciute. Se scrivi una funzione determinante, potrebbe essere difficile dire quale sia il determinante di una matrice 100x100 casuale. Ma tu sai che il determinante della matrice identità è 1, anche se è 100x100. Sai anche che la funzione dovrebbe restituire 0 su una matrice non invertibile (come un 100x100 pieno di tutti gli 0).

3) Usa asserzioni approssimative invece di esatti asseriti. Ho scritto un codice per detta elaborazione SAR che registra due immagini generando punti di legame che creano una mappatura tra immagini e poi facendo una distorsione tra di loro per farle corrispondere. Potrebbe registrarsi a un livello sub-pixel. A priori, è difficile dire qualsiasi cosa su come potrebbe apparire la registrazione di due immagini. Come puoi testarlo? Cose come:

EXPECT_TRUE(register(img1, img2).size() < min(img1.size(), img2.size()))

dato che puoi registrarti solo su parti sovrapposte, l'immagine registrata deve essere più piccola o uguale alla tua immagine più piccola, e inoltre:

scale = 255
EXPECT_PIXEL_EQ_WITH_TOLERANCE(reg(img, img), img, .05*scale)

dato che un'immagine registrata su se stessa dovrebbe essere chiusa a se stessa, ma potresti avere un po 'di più degli errori in virgola mobile a causa dell'algoritmo a portata di mano, quindi controlla che ogni pixel sia entro +/- 5% dell'intervallo dei pixel può assumere (0-255 è in scala di grigi, comune nell'elaborazione delle immagini). Il risultato dovrebbe avere almeno le stesse dimensioni dell'input.

Puoi persino fumare test (ad esempio chiamalo e assicurati che non si arresti). In generale, questa tecnica è migliore per i test di maggiori dimensioni in cui il risultato finale non può essere (facilmente) calcolato a priori per eseguire il test.

4) Utilizza OR STORE un numero casuale di sementi per il tuo RNG.

I run do devono essere riproducibili. È falso, tuttavia, che l'unico modo per ottenere un'esecuzione riproducibile consiste nel fornire un seme specifico a un generatore di numeri casuali. A volte il test di casualità è prezioso. Ho visto / sentito bug nel codice scientifico che emergono in casi degenerati che sono stati generati casualmente (in complicati algoritmi può essere difficile vedere quale sia il caso degenerato è ). Invece di chiamare sempre la tua funzione con lo stesso seme, generare un seme casuale, quindi utilizzare quel seme e registrare il valore del seme. In questo modo, ogni esecuzione ha un seme casuale diverso, ma se si verifica un arresto anomalo, è possibile rieseguire il risultato utilizzando il seme che si è loggato per eseguire il debug. In realtà l'ho usato in pratica e ha schiacciato un bug, quindi ho pensato di parlarne. Certo, questo è successo solo una volta, e sono sicuro che non valga sempre la pena farlo, quindi usa questa tecnica con prudenza. Casuale con lo stesso seme è sempre al sicuro, però. Lato negativo (invece di usare solo lo stesso seme per tutto il tempo): Devi registrare le tue esecuzioni di test. Upside: correzione e bug nuking.

Il tuo caso particolare

1) Prova che un vuoto taskArray restituisce 0 (noto assert).

2) Genera input casuali tali che task.time > 0 , task.due > 0 , e task.importance > 0 per tutti task s, e asserisci che il risultato è maggiore di 0 (rough assert, input casuale) . Non è necessario impazzire e generare semi casuali, il tuo algoritmo non è abbastanza complesso da giustificarlo. Ci sono circa 0 possibilità che pagherebbe: basta mantenere il test semplice.

3) Verifica se task.importance == 0 per tutti task s, quindi il risultato è 0 (noto assert )

4) Altre risposte hanno toccato questo argomento, ma potrebbe essere importante per il tuo caso particolare : Se stai facendo un'API per essere utilizzata dagli utenti al di fuori del tuo team, devi per testare i casi degenerati. Ad esempio, se workPerDay == 0 , assicurati di lanciare un errore carino che informa l'utente dell'input non valido. Se non stai facendo un'API, ed è solo per te e il tuo team, probabilmente puoi saltare questo passaggio e rifiutarti di chiamarlo con il caso degenere.

HTH.

    
risposta data 16.10.2018 - 05:38
fonte
0

Potrebbe sembrare una risposta piuttosto idealistica, ma aiuta a identificare diversi tipi di test.

Se le risposte rigorose sono importanti per l'implementazione, gli esempi e le risposte previste dovrebbero essere fornite nei requisiti che descrivono l'algoritmo. Questi requisiti dovrebbero essere esaminati dal gruppo e se non si ottengono gli stessi risultati, è necessario identificare il motivo.

Anche se stai svolgendo il ruolo di analista e di implementatore, dovresti effettivamente creare dei requisiti e rivederli molto prima di scrivere i test unitari, quindi in questo caso conoscerai i risultati attesi e potrai scrivere i tuoi test di conseguenza.

D'altro canto, se si tratta di una parte che si sta implementando o che non fa parte della business logic o supporta una risposta di business logic, allora dovrebbe andare bene per eseguire il test per vedere quali sono i risultati e quindi modificare il prova di aspettarti questi risultati I risultati finali sono già verificati rispetto ai tuoi requisiti, quindi se sono corretti, tutto il codice che alimenta questi risultati finali deve essere numericamente corretto e, a quel punto, i test di unità sono più utili a rilevare casi di errori di margine e modifiche di refactoring future che a dimostrare che un dato l'algoritmo produce risultati corretti.

    
risposta data 12.10.2018 - 18:39
fonte
0

Penso che sia perfettamente accettabile in alcune occasioni seguire il processo:

  • progetta un caso di test
  • usa il tuo software per ottenere la risposta
  • controlla la risposta a mano
  • scrivere un test di regressione in modo che le versioni future del software continuino a fornire questa risposta.

Questo è un approccio ragionevole in ogni situazione in cui controllare la correttezza di una risposta a mano è più semplice che calcolare la risposta a mano dai primi principi.

Conosco persone che scrivono software per il rendering di pagine stampate e hanno test che controllano che i pixel corretti siano impostati sulla pagina stampata. L'unico modo sensato per farlo è scrivere il codice per rendere la pagina, verificare a occhio d'occhio che sembra buono, e quindi acquisire il risultato come test di regressione per le versioni future.

Solo perché hai letto in un libro che una particolare metodologia incoraggia prima a scrivere i casi di test, non significa che devi sempre farlo in quel modo. Le regole devono essere risolte.

    
risposta data 12.10.2018 - 19:48
fonte
0

Altre risposte Le risposte hanno già delle tecniche per l'aspetto di un test quando il risultato specifico non può essere determinato al di fuori della funzione testata.

Ciò che faccio in aggiunta, che non ho individuato nelle altre risposte, è di generare automaticamente test in qualche modo:

  1. 'Random' input
  2. Iterazione su intervalli di dati
  3. Costruzione di casi di test da serie di limiti
  4. Tutto sopra.

Ad esempio, se la funzione accetta tre parametri ciascuno con intervallo di input consentito [-1,1], prova tutte le combinazioni di ciascun parametro, {-2, -1.01, -1, -0.99, -0.5, -0.01, 0,0.01,0.5,0.99,1,1.01,2, alcuni più casuali in (-1,1)}

In breve: A volte la scarsa qualità può essere sovvenzionata in base alla quantità.

    
risposta data 15.10.2018 - 07:19
fonte

Leggi altre domande sui tag