TDD come approccio ai problemi algoritmici


10

Uno non è riuscito a un test algoritmico con Codility perché ho cercato di trovare una soluzione migliore e alla fine non avevo nulla.

Quindi mi ha fatto pensare se potessi usare un approccio simile al TDD? Vale a dire se di solito riesco a sviluppare una soluzione gradualmente in modo simile?

Se stavo scrivendo un algoritmo di ordinamento, potrei passare da un Bubblesort standard a un bolleort a 2 vie, ma qualcosa di più avanzato come Quicksort sarebbe un "salto di qualità", ma almeno avrei dei dati di test che posso facilmente convalidare.

Altri suggerimenti per tali test? Una cosa che farei la prossima volta è usare più metodi / funzioni rispetto ai circuiti interni. Ad esempio, nell'ordinamento, è spesso necessario uno scambio. Se fosse un metodo avrei solo bisogno di modificare il codice chiamante. Potrei anche avere soluzioni più avanzate come classi derivate.

Con problemi "algoritmici" vs "normali" intendo problemi in cui la complessità temporale è importante. Quindi, invece di superare più test come nel TDD, lo faresti "comportarti meglio".

Con "simile a TDD" intendo:

  1. Scrivi test relativamente automatici per risparmiare tempo sull'incremento pr dei test manuali.
  2. Sviluppo incrementale.
  3. Test di regressione, capacità di rilevare se il codice si interrompe o almeno se la funzionalità cambia tra incrementi.

Penso che questo dovrebbe essere molto facile da capire se si confronta

  1. Scrivere direttamente una shell-sort
  2. Salto da bubblesort a quicksort (riscrittura totale)
  3. Passaggio progressivo da un ordinamento a bolle a senso unico a un ordinamento shell (se possibile).

Cosa intendi con "simile a TDD"? Puoi ovviamente provare a usare TDD per sviluppare una funzione di ordinamento, e quindi utilizzare i test unitari per convalidare la funzione funziona ancora quando sostituisci l'algoritmo di ordinamento con uno più efficiente, ma sembra che tu abbia una domanda diversa in mente?
Doc Brown,

"gradualmente" :-) - Vedi l'ultima frase aggiunta "Quindi invece ..."
Olav

2
Sicuramente puoi provare a risolvere molti problemi con una soluzione funzionante (ma non molto efficiente), quindi migliorarla. Questo non è in alcun modo limitato a problemi algoritmici o di programmazione e non ha molto in comune con TDD. Questo risponde alla tua domanda?
Doc Brown,

@DocBrown No - Vedi l'esempio di Bubblesort / Quicksort. TDD "funziona" bene perché un approccio incrementale funziona bene per molti tipi di problemi. I problemi algoritmici potrebbero essere diversi.
Olav,

Quindi intendevi "è possibile risolvere le domande di progettazione di algoritmi in modo incrementale" (proprio come TDD è un approccio incrementale), e non "da TDD", giusto? Si prega di precisare.
Doc Brown,

Risposte:


9

Vedi anche il tentativo di Ron Jeffries di creare un risolutore di Sudoku con TDD , che purtroppo non ha funzionato.

L'algoritmo richiede una comprensione significativa dei principi di progettazione dell'algoritmo. Con questi principi è infatti possibile procedere in modo incrementale, con un piano, come ha fatto Peter Norvig .

In effetti, per gli algoritmi che richiedono uno sforzo di progettazione non banale, quasi sempre lo sforzo è di natura incrementale. Ma ogni "incremento", che è minuscolo agli occhi di un progettista di algoritmi, sembra un salto di qualità (per prendere in prestito la tua frase) a una persona che non ha avuto la stessa esperienza o conoscenza con questa particolare famiglia di algoritmi.

Ecco perché un'educazione di base nella teoria CS combinata con molte pratiche di programmazione di algoritmi sono ugualmente importanti. Sapere che esiste una particolare "tecnica" (piccoli blocchi di algoritmi) è una lunga strada da percorrere per fare questi salti quantici incrementali.


Vi sono tuttavia alcune importanti differenze tra i progressi incrementali negli algoritmi e nel TDD.

Una delle differenze è stata menzionata da JeffO : un test che verifica la correttezza dei dati di output è separato da un test che afferma le prestazioni tra diverse implementazioni dello stesso algoritmo (o algoritmi diversi che cercano di fornire la stessa soluzione).

Nel TDD, si aggiunge un nuovo requisito sotto forma di test e questo test inizialmente non deve passare (rosso). Quindi il requisito è soddisfatto (verde). Finalmente il codice viene refactored.

Nello sviluppo dell'algoritmo, il requisito di solito non cambia. Il test di verifica della correttezza del risultato viene scritto per primo o poco dopo il completamento di una bozza (altamente sicura ma lenta) dell'algoritmo. Questo test di correttezza dei dati viene raramente modificato; uno non lo cambia per fallire (rosso) come parte del rito TDD.

Tuttavia, in questo aspetto, l'analisi dei dati è nettamente diversa dallo sviluppo dell'algoritmo, poiché i requisiti di analisi dei dati (sia i set di input che i risultati previsti) sono definiti in modo approssimativo nella comprensione umana. Pertanto i requisiti cambiano frequentemente a livello tecnico. Questo rapido cambiamento pone l'analisi dei dati tra lo sviluppo dell'algoritmo e lo sviluppo generale di applicazioni software - sebbene sia ancora pesante per gli algoritmi, i requisiti cambiano anche "troppo velocemente" secondo il gusto di qualsiasi programmatore.

Se il requisito cambia, in genere richiede un algoritmo diverso.

Nello sviluppo dell'algoritmo, cambiare (stringere) il test di confronto delle prestazioni in modo che fallisca (rosso) è sciocco - non ti dà alcuna idea di potenziali modifiche al tuo algoritmo che migliorerebbero le prestazioni.

Pertanto, nello sviluppo dell'algoritmo, sia il test di correttezza che il test delle prestazioni non sono test TDD. Invece, entrambi sono test di regressione . In particolare, il test di regressione della correttezza impedisce di apportare modifiche all'algoritmo che ne interromperà la correttezza; il test delle prestazioni ti impedisce di apportare modifiche all'algoritmo che lo rallenterà.

Puoi ancora incorporare TDD come uno stile di lavoro personale, tranne per il fatto che il rituale "rosso - verde - refattore" non è strettamente necessario né particolarmente utile per il processo di pensiero dello sviluppo dell'algoritmo.

Direi che i miglioramenti dell'algoritmo in realtà derivano dalla realizzazione di permutazioni casuali (non necessariamente corrette) ai diagrammi di flusso di dati dell'algoritmo corrente o dalla loro combinazione e corrispondenza tra implementazioni precedentemente note.


TDD viene utilizzato quando vi sono più requisiti che possono essere aggiunti in modo incrementale al set di test.

In alternativa, se l'algoritmo è basato sui dati, ogni pezzo di dati di test / caso di test può essere aggiunto in modo incrementale. TDD sarebbe anche utile. Per questo motivo un approccio "simile al TDD" di "aggiungere nuovi dati di test - migliorare il codice per farlo gestire correttamente questi dati - refactor" funzionerà anche per il lavoro di analisi dei dati a tempo indeterminato, in cui gli obiettivi degli algoritmi sono descritti nell'uomo -centriche parole e la sua misura di successo anche giudicate in termini definiti dall'uomo.

Pretende di insegnare un modo per renderlo meno travolgente che cercare di soddisfare tutte (dozzine o centinaia) di requisiti in un unico tentativo. In altre parole, TDD è abilitato quando è possibile stabilire che determinati requisiti o obiettivi di estensione possono essere temporaneamente ignorati durante l'implementazione di alcune bozze iniziali della soluzione.

TDD non è un sostituto dell'informatica. È una stampella psicologica che aiuta i programmatori a superare lo shock di dover soddisfare molti requisiti contemporaneamente.

Ma se hai già un'implementazione che dà il risultato corretto, TDD considererebbe il suo obiettivo raggiunto e il codice pronto per essere consegnato (al refactoring o ad un altro programmatore-utente). In un certo senso, ti incoraggia a non ottimizzare prematuramente il tuo codice, dandoti obiettivamente un segnale che il codice è "abbastanza buono" (per superare tutti i test di correttezza).


Nel TDD, ci si concentra anche sui "micro-requisiti" (o qualità nascoste). Ad esempio, convalide dei parametri, asserzioni, lancio e gestione delle eccezioni, ecc. TDD aiuta a garantire la correttezza dei percorsi di esecuzione che non vengono frequentemente esercitati nel normale corso dell'esecuzione del software.

Alcuni tipi di codice algoritmo contengono anche queste cose; questi sono suscettibili di TDD. Ma poiché il flusso di lavoro generale dell'algoritmo non è TDD, tali test (su convalide dei parametri, asserzioni e lancio e gestione delle eccezioni) tendono a essere scritti dopo che il codice di implementazione è già stato (almeno in parte) scritto.


Che cosa significano le prime due parole citate nel tuo post ("Zio Bob")?
Robert Harvey,

@RobertHarvey Secondo lo zio Bob, TDD può essere utilizzato per la scoperta di algoritmi. Secondo un altro luminare, non funziona. Ho pensato che entrambi dovrebbero essere menzionati (cioè ogni volta che qualcuno menziona un esempio, uno è anche obbligato a menzionare l'altro esempio) in modo che le persone ottengano informazioni equilibrate su esempi positivi e negativi.
rwong

OK. Ma capisci la mia confusione? Il tuo primo paragrafo sembra citare qualcuno che pronuncia le parole "Zio Bob". Chi lo dice?
Robert Harvey,

L'ordine di @RobertHarvey è stato rispettato.
rwong

2

Per il tuo problema, avresti due test:

  1. Un test per assicurarsi che l'algoritmo sia ancora accurato. ad es. è ordinato correttamente?
  2. Test di confronto delle prestazioni: le prestazioni sono migliorate. Questo può diventare complicato, quindi aiuta a eseguire il test sulla stessa macchina, gli stessi dati e limitare l'uso di altre risorse. Una macchina dedicata aiuta.

Cosa testare o la copertura completa del test è discutibile, ma penso che le parti complesse della tua applicazione che devono essere messe a punto (essere cambiate molto) siano candidati perfetti per i test automatizzati. Queste parti dell'applicazione vengono generalmente identificate molto presto. Usare un approccio TDD con questo pezzo avrebbe senso.

Essere in grado di risolvere problemi complessi non dovrebbe essere ostacolato da una riluttanza a provare vari approcci. Avere un test automatizzato dovrebbe aiutare in questo settore. Almeno saprai che non stai peggiorando le cose.


1

Quindi, invece di superare più test come nel TDD, lo faresti "comportarti meglio".

Una specie di.

È possibile verificare il tempo di esecuzione e la complessità. I test di runtime dovranno essere un po 'tolleranti per consentire la contesa sulle risorse di sistema. Tuttavia, in molte lingue, è anche possibile ignorare o iniettare metodi e contare le chiamate di funzioni effettive. E, se sei sicuro che il tuo attuale algoritmo non sia ottimale, puoi introdurre un test che richiede semplicemente che sort()invochi compare()meno volte rispetto all'implementazione ingenua.

In alternativa, puoi confrontare sort()su due set e assicurarti che vengano ridimensionati nelle compare()chiamate in base alla complessità del tuo target (o più tardi, se ti aspetti qualche incoerenza).

E, se puoi teoricamente dimostrare che un set di dimensioni non Ndovrebbe richiedere nient'altro che N*log(N)confronti, potrebbe essere ragionevole limitare il tuo già funzionante sort()alle N*log(N)invocazioni di compare()...

Però ...

Soddisfare un requisito di prestazioni o complessità non garantisce che l'implementazione sottostante sia {AlgorithmX} . E, direi che questo è normalmente OK. Non dovrebbe importare quale algoritmo viene utilizzato, a condizione che l'implementazione soddisfi i requisiti richiesti, inclusi requisiti di complessità, prestazioni o risorse importanti.

Ma, se vuoi assicurarti che venga utilizzato un particolare algoritmo, dovrai essere più noioso e ottenere molto più in profondità. Cose come..

  • Garantire che venga effettuato esattamente il numero previsto di chiamate verso compare()e swap()(o qualsiasi altra cosa)
  • Avvolgere tutte le principali funzioni / metodi e garantire, con un set di dati prevedibile, che le chiamate avvengano esattamente nell'ordine previsto
  • Esame dello stato di funzionamento dopo ciascuna delle N fasi per assicurarsi che sia cambiato esattamente come previsto

Ma ancora una volta, se stai cercando di assicurarti che {AlgorithmX} sia usato in particolare, probabilmente ci sono caratteristiche di {AlgorithmX} che ti interessano che sono più importanti da testare rispetto al fatto che {AlgorithmX} sia stato effettivamente usato alla fine ...


Inoltre, tieni presente che TDD non annulla la necessità di ricercare, pensare o pianificare lo sviluppo del software. Inoltre, non nega la necessità di brainstorming e sperimentazione, anche se non puoi affermare facilmente su Google e la lavagna nella suite di test automatizzati.


Punto eccellente per quanto riguarda la possibilità di imporre limiti al numero di operazioni. Direi che è il potere di (beffe, tronconi e spie), qualcosa che può essere utilizzato in TDD, che può essere utilizzato anche in altri tipi di test.
rwong

Non vedo molto senso scrivere test prima del codice in uno scenario di test. Di solito è un ultimo criterio dopo che i criteri funzionali sono stati soddisfatti. A volte potresti fare numeri casuali prima e nel caso peggiore dopo, ma comunque scrivere test richiederebbe molto tempo rispetto ad alcune stampe intelligenti. (Con alcune statistiche potresti probabilmente scrivere del codice generico, ma non durante un test) Nel mondo reale penso che vorresti sapere se le prestazioni diminuiscono improvvisamente in alcuni casi.
Olav,

Se guardi codility.com/programmers/task/stone_wall sapresti se hai una complessità maggiore di N, ad esempio casi particolari in cui devi lavorare su intervalli molto lunghi.
Olav,

@Olav "scrivere test richiederebbe molto tempo rispetto ad alcune stampe intelligenti" ... da fare una volta ... ehm ... forse , ma anche molto discutibile. Fare ripetutamente ad ogni build? ... Sicuramente no.
svidgen,

@Olav "Nel mondo reale penso che vorresti sapere se le prestazioni diminuiscono improvvisamente in alcuni casi." Nei servizi live, useresti alcuni come New Relic per monitorare le prestazioni complessive , non solo per determinati metodi. E idealmente, i tuoi test ti diranno quando moduli e metodi critici per le prestazioni non riescono a soddisfare le aspettative prima della distribuzione.
svidgen,
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.