Sviluppo basato su test durante l'implementazione di un elenco di lunghezza flessibile

5

Secondo la strategia TDD comunemente usata, per implementare qualcosa, si scrive un test che fallisce il codice prima, scrivi il codice più semplice, refactoring e poi ripeti. Sto cercando di immaginare questo scenario con l'implementazione di un elenco di lunghezza flessibile (ad esempio List<T> in. net.

Diciamo che prima provo inserendo un oggetto. Probabilmente il modo più semplice per ottenerlo è il backing della lista con un array di lunghezza 1 (che supererà il test). Nulla da rifattorici qui, quindi vado avanti e scrivo un altro test che inserisce 2 elementi. Cambierò semplicemente la lunghezza dell'array su 2 e il test passerà di nuovo. Quindi scrivo test con 3 elementi, espandi l'array e ripeto. Finirò per sempre a fare questo finché non sono stanco.

Questa è un'eccezione alla strategia di fail-test-first? O mi manca qualcosa nella strategia sopra?

PS: L'implementazione effettiva esegue il back-up della lista con una matrice che cresce due volte più grande ogni volta che il numero di elementi supera la lunghezza dell'array.

    
posta Louis Rhys 31.01.2013 - 09:18
fonte

7 risposte

3

"Scrivi la cosa più semplice che potrebbe funzionare" è un principio uno , ma non è l'unico, e non è sempre il più importante. "Non ripeterti" è probabilmente più importante, e ripeterti nel modo in cui descrivi non è assolutamente giustificato dal "più semplice". Non appena si nota la ripetizione, è necessario passare a una soluzione in grado di gestire più di una lunghezza della lista.

Si noti che questa soluzione potrebbe benissimo essere un array a lunghezza fissa se si sa che non sarà necessario più di un dato numero di elementi in base ai requisiti. In alternativa, potrebbe essere una struttura dinamica che cresce con le richieste fatte su di esso. Ma in nessun caso è utile scalare l'ultimo degli interi uno per uno - non ultimo perché non è possibile raggiungere la cima.

    
risposta data 31.01.2013 - 09:30
fonte
2

Ok, quindi stai facendo quello che pensi sia la cosa più semplice che funzioni, ma non hai definito il tuo problema in modo adeguato.

Trasforma il problema e guarda cosa hai. "Implementare un clone di lista" ha molte sfumature che devono essere ampliate e documentate. Qual è lo scopo di questo clone di List? Quale numero di articoli è previsto per supportare?

Generalmente le risposte a queste e ad altre domande escono all'inizio. Ad esempio, chiediti perché stai implementando una sostituzione di List? Se è solo un esercizio di codifica, è necessario dare un po 'di spessore a uno scenario. Nel mondo reale potresti farlo perché l'implementazione List esistente non presenta alcune caratteristiche che desideri. Potrebbe essere troppo lento o non ordinato o incapace di far fronte a un gazillion items o cosa mai. Questo è ciò che ti manca qui.

Per risolvere il problema, come saprai che hai finito? Quali sono i tuoi criteri di accettazione? Cosa significa "Working and complete" per questo problema? TDD può eliminare problemi come quello che succede se provi ad accedere ad un oggetto nella posizione "-3.2", ma in realtà non è necessario. Quello che si deve fare è assicurarsi che il proprio codice a) faccia ciò che si prevede di fare eb) continui a fare ciò che ci si aspetta da lui quando lo si rifatta o lo si sostituisce.

    
risposta data 31.01.2013 - 12:22
fonte
1

Puoi utilizzare for nei test. Creerei un test che inserisce un elemento. E poi un altro test che inserisce un numero casuale di elementi (dove count > size di ridistribuzione).

Tuttavia, non inizierei mai con una dimensione di array interna di 1. Non ha senso, è una lista, non è vero?

    
risposta data 31.01.2013 - 09:30
fonte
1

"I'll go ahead and write another test that insert 2 items. (…) Then I write test with 3 items, expand the array, and repeat again. I will end up forever doing this until I'm tired."

Il problema qui non è che la tua implementazione di List<T> è difettosa, ma che i tuoi test unitari stanno specificando il tipo sbagliato di implementazione.

Se consideri questi test unitari una specifica per la tua implementazione List<T> , hai appena specificato qualcosa del tipo:

  • Può inserire 1 elemento nella lista vuota, dopo di che conterrà 1 elemento.
  • Può inserire 2 elementi nella lista vuota, dopo di che conterrà 2 elementi.
  • Può inserire 3 elementi nella lista vuota, dopo di che conterrà 3 elementi.
  • ecc.

E una volta superati i test, la tua lista sarà in grado di soddisfare esattamente le specifiche. Ma è davvero questo quello che vuoi?

O preferiresti forse una specifica più generale, come la seguente?:

  • Può inserire 1 elemento nella lista vuota, dopo di che conterrà 1 elemento.
  • Puoi inserire 1 elemento in una lista con elementi n , dopodiché conterrà n +1 elementi.

Se è così, scrivi test di unità differenti che codificano che invece (e scoprirai che non hai più bisogno di infiniti test, ma che in realtà sei finito dopo due).

    
risposta data 01.09.2013 - 09:46
fonte
0

Se la lunghezza flessibile è importante, il modo migliore che posso pensare è scrivere un test generale come funzione con argomento il numero di voci. Quindi la natura del tuo problema dovrebbe dirti quali numeri sono importanti da testare. I buoni candidati sono i numeri 0 e 1. In generale, si desidera testare ciò che Robert Martin chiama "condizioni al contorno" nel suo grande libro "Codice pulito", descritto come:

Boundary is what separates the known from the unknown

L'altro lato del confine nel tuo caso sarebbe un numero elevato di voci, ad esempio il più grande che puoi aspettarti, per vedere che il tuo sistema può farcela.

    
risposta data 01.09.2013 - 05:22
fonte
0

TL; DR

Il test è più un'arte che una scienza. Come costruire un test è in realtà meno importante di sapere cosa è utile da testare.

Comportamento del test

Comportamento del test, non dettagli dell'implementazione. Quale comportamento stai effettivamente cercando di testare? Con ogni probabilità, la tua funzione deve fare solo due cose: creare un elenco ed estrarre i dati dall'elenco.

Da un punto di vista pragmatico, non importa quanto sia implementato il tuo elenco. Quello che vuoi veramente sapere è se i tuoi input producono risultati attesi. Ad esempio:

  1. Creazione di elenchi

    Dati elementi dati x, yez
    Quando chiamo il metodo di creazione con quegli elementi
    Quindi il metodo restituisce una lista che corrisponde al mio oggetto fixture.

  2. Elenca estrazione

    Dato un elenco contenente 50 elementi di fissaggio
    Quando guardo il 43 ° elemento
    Allora dovrei trovare il mio valore previsto.

Nella maggior parte dei casi, i requisiti non funzionali possono essere descritti anche in termini comportamentali.

Verifica condizioni al contorno

Generalmente non è utile testare l'infinito o testare esaurientemente ogni possibile input o valore intermedio in un sistema. D'altra parte, è spesso utile pensare a condizioni al contorno come:

  • Un elenco senza elementi di dati.
  • Un elenco con un numero molto piccolo di elementi di dati.
  • Un elenco con un numero molto elevato di elementi di dati.

Un test è in realtà un'ipotesi che stai convalidando, quindi è bene fare ipotesi documentate. Se è improbabile che tu abbia più di 500 elementi nel tuo elenco, puoi definire dei limiti agli elementi 499, 500 e 501 che vale la pena di provare; tuttavia, non c'è quasi alcun valore nel testare esplicitamente 13.756 elementi in questo scenario perché è già coperto dai test di confine.

    
risposta data 01.09.2013 - 09:28
fonte
0

Penso che tu stia confondendo test unitari con test basati sulle proprietà nella tua domanda.

Test delle unità guarda una classe come se fosse un'unità di elaborazione in una catena di elaborazione del segnale , come un circuito amplificatore o un rack effetti per chitarra. Penso che Kent Beck abbia usato esattamente questa metafora nel suo lavoro " Test Driven Development: per esempio ", ma al momento non ho una copia del libro da controllare.

Il punto è spiegare che è più facile controllare che un determinato filtro tenga sempre le alte frequenze, piuttosto che verificare se una combinazione specifica di molti filtri e distorsioni ti fa sembrare esattamente come Jimi Hendrix.

Applicato al tuo caso, potresti voler testare in un unit test che una classe List possa add elementi se necessario. Essendo rigoroso con la filosofia TDD, dovresti fare in modo che List tenga già un numero casuale di elementi prima di aggiungerlo, altrimenti un'implementazione minima potrebbe conoscere il numero totale previsto e solo restituirlo .

Test basato su proprietà , d'altra parte, controlla che una funzione sia matematicamente ben definita nel senso che restituisce ciò che è previsto per l'intero dominio in cui è definito (la definizione qui è mio e potrebbe essere inaccurato). Puoi avere un'idea di come funziona leggendo su QuickCheck e ScalaCheck .

Il concetto base è che se passi al framework di test una funzione che ha una firma tipizzata, ad es. sum(one: Int, another: Int) , quindi il framework genera molti test attraversando tutte le possibili varianti del tipo . Nell'esempio sum , Int va da Int.MinValue a Int.MaxValue : il framework seleziona, ad esempio, 100 coppie di valori e prova a sum di esse. Per classi più complesse, potresti dover dire al framework come creare un'istanza Arbitraria della tua classe.

Applicato al tuo caso, potresti voler mostrare che il length del tuo List è sempre aumentato di uno dopo un'operazione di add . Definisci un elenco arbitrario come un elenco che contiene già un numero casuale di elementi e definisci che dopo un add questo numero viene aumentato di 1.

Il vantaggio principale dei test basati sulle proprietà è che ottieni automaticamente molti test scritti automaticamente e puoi persino controllare la generazione per cercare casi limite. Il test basato sulla proprietà genererà un elenco di 100 e controllerà che l'invariant sia valido; il test unitario dovrebbe essere ripetuto 100 volte al fine di darti tale garanzia. Tuttavia, i test basati sulle proprietà non sono una pallottola d'argento, è solo un complemento per i test unitari in determinate situazioni.

    
risposta data 14.04.2014 - 12:43
fonte

Leggi altre domande sui tag