Domanda

Dato un software di ...

  • Il sistema consiste in un paio di sottosistemi
  • Ogni sottosistema è costituito da alcuni componenti
  • Ogni componente è implementato usando molte classi

...Mi piace scrivere dei test automatizzati, per ciascun sottosistema o componente.

Non scrivo un test per ogni classe interna di un componente (ad eccezione, in quanto ogni classe contribuisce alla componente pubblica di funzionalità ed è quindi verificabile/testato dall'esterno tramite la componente pubblica di API).

Quando ho refactor l'implementazione di un componente (che faccio spesso, come l'aggiunta di nuove funzionalità), ho quindi non c'è bisogno di modificare qualsiasi esistente test automatici:perché il test dipende solo dalla componente pubblica, API e Api pubbliche sono in genere in fase di espansione piuttosto alterato.

Credo che questa politica contrasti con un documento come il Refactoring Del Codice Di Test, che dice le cose come ...

  • "...i test di unità ..."
  • "...una classe di test per ogni classe nel sistema ..."
  • "...prova codice / codice di produzione rapporto ...è idealmente considerato l'approccio di un rapporto di 1:1 ..."

...tutti che suppongo non sono d'accordo con (o almeno non la pratica).

La mia domanda è, se non siete d'accordo con la mia politica, può spiegare perché?In quali scenari è questo grado di prova insufficiente?

In sintesi:

  • Interfacce pubbliche sono provato e riprovato), e raramente cambiano (si è aggiunto, ma raramente alterati)
  • Api interne sono nascosti dietro le Api pubbliche, e possono essere modificati senza riscrivere i casi di test che prova le Api pubbliche

Nota a piè di pagina:alcuni dei miei "test case" sono in realtà costituiti da dati.Per esempio, casi di test per l'interfaccia utente consiste di file di dati che contengono vari input dell'utente e la corrispondente previsto uscite di sistema.Prova il sistema significa avere un codice di test che analizza tutti i file di replay l'ingresso nel sistema, e afferma che si ottiene il corrispondente output atteso.

Anche se raramente ho bisogno di modificare il codice di test (perché Api pubbliche sono di solito aggiunti piuttosto che modificato), io trovo che a volte (ad esempiodue volte a settimana) necessità di modificare alcuni file di dati esistenti.Questo può succedere quando si cambia il sistema di uscita per il meglio (cioènuove funzionalità migliora output esistente), che potrebbero provocare un test esistenti per 'errore' (perché il codice di test, solo cerca di affermare che la produzione non è cambiato).Per gestire questi casi io faccio il seguente:

  • Eseguire nuovamente il test automatizzato suite, con un run-time di bandiera, che gli dice di non far valere l'uscita, ma invece di catturare la nuova uscita in una nuova directory
  • Utilizzare un visual diff strumento per vedere quali dati di output di file (ad esempioche i casi di test) sono cambiati, e per verificare che questi cambiamenti sono buoni e, come previsto, dato che la nuova funzionalità di
  • Aggiornare gli attuali test di copiare i nuovi file di output da la nuova directory nella directory da cui i casi di test eseguiti (sovra-scrittura dei vecchi test)

Nota a piè di pagina:da "componente", intendo qualcosa di simile a "una DLL" o "il gruppo" ...qualcosa che è abbastanza grande da essere visibile su un'architettura o un diagramma di distribuzione del sistema, spesso implementata con decine o 100 classi, e con un pubblico di API che si compone di circa 1 o una manciata di interfacce ...qualcosa che può essere assegnato ad un team di sviluppatori (in cui un componente diverso è assegnato a un team diverso), e che quindi secondo Conway della Legge avere un relativamente stabile API pubblica.


Nota a piè di pagina:L'articolo Object-Oriented Test:Mito e Realtà dice,

Mito:Prova di scatola nera è sufficiente. Se si fa un attento lavoro di test case design utilizzando l'interfaccia della classe o specifica, si può essere certi che la classe è stato esercitato.White-box testing (guardando un implementazione del metodo di progettazione test) viola il concetto stesso di l'incapsulamento.

Realtà:OO struttura della materia, parte II. Molti studi hanno dimostrato che scatola nera suite di test pensato per essere estremamente approfondita dagli sviluppatori esercitare solo da un terzo alla metà delle dichiarazioni (per non parlare dei percorsi o stati) in attuazione di sotto test.Ci sono tre ragioni per questo.Primo, ingressi o di stati selezionato in genere l'esercizio normale i percorsi, ma non la forza di tutti i possibili percorsi/stati.Secondo, scatola nera il test da solo non può rivelare sorprese.Supponiamo che abbiamo testato in tutte le specificato comportamenti del sistema sotto test.Per essere sicuri ci sono no non specificato comportamenti di cui abbiamo bisogno per sapere se tutte le parti del sistema sono non è stato esercitato dalla scatola nera suite di test.Il solo modo in cui questo informazioni possono essere ottenute è da codice la strumentazione.In terzo luogo, è spesso difficile l'esercizio eccezione e gestione degli errori senza esame di il codice sorgente.

Devo aggiungere che sto facendo whitebox test funzionali:Vedo il codice (in attuazione) e scrivo i test funzionali (che in auto le API pubbliche) ad esercitare le varie diramazioni del codice (per la caratteristica dell'attuazione).

È stato utile?

Soluzione

La mia pratica è quello di testare l'interno attraverso il pubblico API / UI. Se qualche codice interno non è raggiungibile dall'esterno, poi ho refactoring per rimuoverlo.

Altri suggerimenti

La risposta è molto semplice: si sta descrivendo collaudo funzionale, che è una parte importante di QA software. Testare implementazione interna è unit test, che è un'altra parte del QA software con un obiettivo differente. Ecco perché si sente che le persone sono d'accordo con il vostro approccio.

Test funzionale è importante verificare che il sistema o il sottosistema fa quello che dovrebbe fare. Tutto ciò che il cliente vede dovrebbe essere testato in questo modo.

Unit-test è qui per verificare che le 10 righe di codice appena scritto fa quello che dovrebbe fare. Ti dà una maggiore fiducia sul vostro codice.

Entrambi sono complementari. Se si lavora su un sistema esistente, collaudo funzionale è la prima cosa su cui lavorare, probabilmente. Ma non appena si aggiunge il codice, unit test è una buona idea anche.

Non ho la mia copia di Lakos davanti a me, quindi piuttosto che cito mi limito a rilevare che lui fa un lavoro migliore di quello che sarà di spiegare il motivo per cui il test è importante a tutti i livelli.

Il problema solo con i test "comportamento pubblico" è un test del genere ti dà informazioni molto poco. Si prenderà molti bug (proprio come il compilatore catturerà molti bug), ma non si può dire dove gli insetti sono. E 'comune per un'unità mal implementato per tornare buoni valori per un lungo periodo di tempo e poi smettere di fare in modo che quando le condizioni cambiano; se tale unità era stato testato direttamente, il fatto che è stata implementata male sarebbe stato evidente prima.

Il miglior livello di granularità di prova è il livello di unità. Fornire test per ciascuna unità mediante l'interfaccia (s). Questo permette di analizzare e documentare le proprie convinzioni su come ogni componente si comporta, che a sua volta permette di testare codice dipendente dal solo testare la nuova funzionalità che introduce, che a sua volta mantiene test breve e sul bersaglio. Come bonus, mantiene test con il codice che stanno testando.

Per frase in modo diverso, è giusto provare solo il comportamento del pubblico, fino a quando si noterà che ogni classe pubblicamente visibile ha un comportamento pubblico.

Ci sono stati un sacco di grandi risposte a questa domanda fino ad ora, ma voglio aggiungere alcune note del mio. Come prefazione: Io sono un consulente per una grande azienda che fornisce soluzioni tecnologiche per una vasta gamma di clienti di grandi dimensioni. Dico questo perché, nella mia esperienza, ci viene richiesto di testare molto più a fondo di quanto la maggior parte dei negozi di software non (salvo forse gli sviluppatori API). Ecco alcuni dei passi che attraversiamo per garantire la qualità:

  • Unità interna di prova:
    Gli sviluppatori sono tenuti a creare test di unità per tutto il codice che scrivono (leggi: ogni metodo). I test unitari dovrebbero coprire condizioni di test positivi (il mio metodo funziona?) E condizioni di prova negativi (non il metodo gettare un'ArgumentNullException quando uno dei miei argomenti obbligatori è nullo?). Noi di solito incorporare questi test nel processo di generazione utilizzando uno strumento come CruiseControl.net
  • Test System / Test Assembly:
    A volte questo passo si chiama qualcosa di diverso, ma questo è quando cominciamo il test della funzionalità pubblico. Una volta che sai tutta la vostra funzione di unità individuali come previsto, si vuole sapere che le vostre funzioni esterne lavorano anche il modo in cui si pensa che dovrebbero. Si tratta di una forma di verifica funzionale dal momento che l'obiettivo è quello di determinare se l'intero sistema funziona come dovrebbe. Si noti che questo non include alcun punti di integrazione. Per test di sistema, si dovrebbe utilizzare interfacce deriso-up al posto di quelli veri in modo che è possibile controllare l'output e costruire casi di test intorno ad esso.
  • Test System Integration:
    In questa fase del processo, si desidera collegare i punti di integrazione al sistema. Ad esempio, se si sta utilizzando un sistema di elaborazione di carta di credito, ti consigliamo di integrare il sistema live, in questa fase per verificare che funziona ancora. Si consiglia di eseguire test simile al sistema di test / montaggio.
  • Funzionale test di verifica:
    la verifica funzionale è utenti che attraversano il sistema o utilizzando l'API per verificare che funziona come previsto. Se avete costruito un sistema di fatturazione, questa è la fase in cui si eseguire i vostri script di test da un capo all'altro per garantire che tutto funziona come avete progettato. Si tratta ovviamente di una fase critica del processo dal momento che ti dice se hai fatto il tuo lavoro.
  • Test di certificazione:
    Qui, si mette gli utenti reali di fronte del sistema e lasciare 'em avere un andare a esso. Idealmente hai già provato l'interfaccia utente ad un certo punto con le parti interessate, ma questa fase vi dirà se il vostro target di riferimento piace il vostro prodotto. Potreste aver sentito questo chiamato qualcosa come una "release candidate" di altri fornitori. Se tutto va bene, in questa fase, si sa che si sta bene ad andare in produzione. prove di certificazione devono essere sempre eseguite nello stesso ambiente che verrà usato per la produzione (o di un ambiente identico, almeno).

Certo, lo so che non tutti segue questo processo, ma se la si guarda da un capo all'altro, si può cominciare a vedere i benefici dei singoli componenti. Non ho incluso le cose come test di verifica costruire dal accadono su una linea temporale diversa (ad esempio, tutti i giorni). Personalmente credo che i test di unità sono critiche, perché ti danno una visione in profondità nella quale componente specifico dell'applicazione sta fallendo in cui specifici casi d'uso. Prove di unità vi aiuterà anche a isolare quali metodi funzionano correttamente in modo da non spendere tempo alla ricerca di loro per ulteriori informazioni su un errore quando non c'è niente di sbagliato con loro.

Naturalmente, unit test potrebbe anche essere sbagliata, ma se si sviluppano i vostri casi di test dalla specifica funzionale / tecnica (ne avete uno, a destra;?)), Non dovreste avere troppi problemi.

Se si sta praticando puro sviluppo test-driven allora implementare solo qualsiasi codice dopo avete qualsiasi prova mancanza, e solo implementare il codice di prova quando non si hanno prove di fallimento. Inoltre implementare solo la cosa più semplice per fare un test in mancanza o di passaggio.

Nella pratica TDD limitata che ho avuto ho visto come questo mi aiuta a scovare le prove di unità per ogni condizione logica prodotta dal codice. Non sono del tutto sicuro che il 100% delle funzionalità logiche del mio codice privato è esposto dai miei interfacce pubbliche. Praticare TDD sembra omaggio a quella metrica, ma ci sono caratteristiche ancora nascosti non consentito dalle API pubbliche.

suppongo che si possa dire che questa pratica mi protegge contro i difetti futuri nei miei interfacce pubbliche. O si trova che utile (e consente di aggiungere nuove funzionalità più rapidamente) o ci si accorge che si tratta di una perdita di tempo.

È possibile codificare i test funzionali; va bene. Ma si dovrebbe convalidare utilizzando la copertura dei test sulla realizzazione, per dimostrare che il codice in fase di test tutto ha uno scopo relativo ai test funzionali, e che lo fa in realtà qualcosa di rilevante.

Non si deve pensare alla cieca che un'unità == una classe. Penso che possa essere controproducente. Quando dico che scrivo una prova di unità sto testando un'unità logica - "qualcosa" che fornisce alcuni comportamenti. Un'unità può essere una singola classe, o può essere più classi lavorano insieme per fornire tale comportamento. A volte si inizia come una singola classe, ma si evolve per diventare tre o quattro classi in seguito.

Se comincio con una classe e scrivere dei test per questo, ma in seguito diventa più classi, io di solito non scrivo le prove separate per le altre classi - sono dettagli di implementazione dell'unità in fase di sperimentazione. In questo modo ho il mio disegno permette di crescere, e le mie prove non sono così fragile.

Ho usato a pensare esattamente come demonstartes CrisW in questa domanda - che i test a livelli più alti sarebbe meglio, ma dopo ottenere un certo più esperienza i miei pensieri sono moderati a qualcosa tra questo e "ogni classe deve avere una classe di test". Ogni unità deve avere le prove, ma scegliere di definire le mie unità leggermente diverso da quello che ho fatto una volta. Potrebbe essere i "componenti" parla CrisW circa, ma molto spesso è anche solo una singola classe.

Inoltre, i test funzionali possono essere sufficiente per dimostrare che il sistema fa quello che dovrebbe fare, ma se si vuole guidare il vostro disegno con gli esempi / test (TDD / BDD), test di leva inferiore sono una conseguenza naturale. Si potrebbe buttare quei test di basso livello via quando hai finito di attuazione, ma che sarebbe uno spreco - i test sono un effetto collaterale positivo. Se si decide di fare refactoring drastiche invalidare i test di basso livello, allora si gettano via e scrivere nuova volta.

Separare l'obiettivo di testing / provare il software, e l'utilizzo di test / esempi per guidare il vostro disegno / implementazione in grado di chiarire questo discussione molto.

Aggiornamento: Inoltre, ci sono fondamentalmente due modi di fare TDD: outside-in e dentro-fuori. BDD promuove outside-in, che porta a più alto livello test / specifiche. Se si inizia dai dettagli tuttavia, sarà scrivere i test dettagliati per tutte le classi.

Sono d'accordo con la maggior parte dei messaggi su qui, tuttavia vorrei aggiungere questo:

V'è una priorità primaria per testare interfacce pubbliche, quindi protetto, quindi privata.

Di solito le interfacce pubbliche e protette sono una sintesi di una combinazione di interfacce private e protette.

Personalmente: Si dovrebbe provare tutto. Dato un insieme forte di test per le funzioni più piccoli, vi sarà data una maggiore fiducia che che i metodi nascosti funzionano. Inoltre sono d'accordo con il commento di un'altra persona circa refactoring. copertura del codice vi aiuterà a determinare dove i bit extra di codice sono e di refactoring quelli fuori se necessario.

Stai ancora seguendo questo approccio? Credo anche che questo è l'approccio giusto. Si consiglia di verificare solo interfacce pubbliche. Ora interfaccia pubblica può essere un servizio o di qualche componente che prende input da un qualche tipo di interfaccia utente o di qualsiasi altra fonte.

Ma si dovrebbe essere in grado di evolvere il servizio puplic o componente utilizzando il test di primo approccio. vale a dire definire un'interfaccia pubblica e testarlo per funzionalità di base. fallirà. Implementare funzionalità di base che utilizzando le classi di sfondo API. Scrivi API per soddisfare solo questo banco di prova firt. Poi continuare a chiedere ciò che il servizio può fare di più e si evolvono.

Solo bilanciamento decisione che dovrebbe essere presa è di rompere il un grande servizio o componente in pochi servizi più piccoli e dei componenti che possono essere riutilizzati. Se credi fortemente un componente può essere riutilizzato attraversato progetti. Poi test automatizzati dovrebbero essere scritti per quel componente. Ma ancora una volta le prove scritte per il grande servizio o componente dovrebbe duplicare il functionalitly già testato come componente.

Alcune persone possono andare in discussione theorotical che questo non è unit testing. Quindi, questo è bene. L'idea di base è quella di aver prove che testano il software automatizzato. Che importa se la sua non a unità di livello. Se si estende l'integrazione con database (cui è possibile controllare) allora il suo solo meglio.

Fatemi sapere se si è sviluppato alcun buon processo che funziona per you..since il tuo primo post ..

saluti Ameet

Io personalmente prova Parti protette troppo, perché sono "pubblico" per i tipi ereditati ...

Sono d'accordo che la copertura del codice dovrebbe essere idealmente al 100%. Questo non significa necessariamente 60 righe di codice avrebbero 60 righe di codice di prova, ma che ogni percorso di esecuzione viene testato. L'unica cosa di più fastidioso di un bug è un bug che non ha eseguito ancora.

Con solo testare l'API pubblica si corre questo rischio di non testare tutte le istanze delle classi interne. Sono davvero affermare l'ovvio dicendo che, ma penso che dovrebbe essere menzionato. Il più ogni comportamento è testato, più facile è quello di riconoscere non solo che si è rotto, ma ciò che è rotto.

I test privati di attuazione, così come i dettagli di interfacce pubbliche.Se cambio un dettaglio di implementazione e la nuova versione ha un bug, questo mi permette di avere una migliore idea di dove l'errore è in realtà e non solo di ciò che si sta effettuando.

[Una risposta alla mia domanda]

Forse una delle variabili che conta molto è il numero di programmatori diversi si esegue la codifica:

  • Axiom: ogni programmatore dovrebbe testare il proprio codice

  • Quindi: se un programmatore scrive e uno offre "unità", quindi essi dovrebbero anche avere testato tale unità, molto probabilmente scrivendo una "unit test"

  • Corollario: se un singolo programmatore scrive un intero pacchetto, allora è sufficiente per il programmatore di scrivere i test funzionali di tutto il pacchetto (non c'è bisogno di scrivere test "unità" di unità all'interno del pacchetto, dal momento che tali unità sono dettagli di implementazione a cui altri programmatori non hanno accesso / esposizione diretta).

Allo stesso modo, la pratica della costruzione di componenti "finti", che è possibile testare contro:

  • Se si dispone di due squadre costruzione di due componenti, ognuno può avere bisogno di "finta" componente dell'altro in modo che abbiano qualcosa (il finto) contro il quale mettere alla prova la propria componente, prima che il loro componente è ritenuto pronto per la successiva "test di integrazione", e prima che l'altra squadra ha consegnato loro componente contro il quale il componente può essere testato.

  • Se si sta sviluppando l'intero sistema, allora si può crescere l'intero sistema ... per esempio, sviluppare un nuovo campo di interfaccia grafica, un nuovo campo di database, una nuova transazione di business, e un nuovo sistema / test funzionale , il tutto come parte di un'iterazione, senza la necessità di sviluppare "prende in giro" di qualsiasi livello (dato che è possibile testare contro la cosa reale, invece).

  

Axiom: ogni programmatore dovrebbe testare il proprio codice

Non credo che questo è universalmente vero.

In crittografia, c'è un noto detto: "è facile creare un cifrario in modo da proteggere non si sa come rompere da te"

Nel processo di sviluppo tipico, si scrive il codice, quindi compilare ed eseguire per verificare che fa quello che pensi lo fa. Ripetere questa operazione un po 'di tempo e vi sentirete abbastanza fiducioso circa il vostro codice.

La vostra fiducia vi farà un tester meno vigile. Uno che non condivide la vostra esperienza con il codice non avrà il problema.

Inoltre, un nuovo paio di occhi può avere meno pregiudizi non solo circa l'affidabilità del codice, ma anche su ciò che il codice fa. Di conseguenza, essi possono venire con casi di test l'autore del codice non ha pensato. Ci si aspetterebbe quelli a uno scoprire le più bug, o la conoscenza diffusa di quello che fa il codice intorno l'organizzazione un po 'di più.

In aggiunta, c'è un argomento per essere fatto che per essere un buon programmatore si deve preoccupare di casi limite, ma per essere un buon tester avete preoccupazione ossessivamente ;-) anche, i tester possono essere più conveniente, quindi potrebbe essere la pena avere un team di test separato per questo motivo.

Credo che la domanda generale è questo: che la metodologia è la migliore a trovare bug nel software? Recentemente ho guardato un video (nessun collegamento, sorry) affermando che il test randomizzato è più conveniente che e efficace come i test umani generati.

Dipende dal vostro disegno e dove il valore più grande sarà. Un tipo di applicazione può richiedere un approccio diverso da un altro. A volte si a malapena prendere qualcosa di interessante con test di unità, mentre funzionale / test di integrazione resa sorprese. A volte i test di unità non riescono centinaia di volte durante lo sviluppo, la cattura di molti, molti bug nel processo decisionale.

A volte è banale. Il modo in cui alcune classi appendere insieme rende il ritorno sugli investimenti di testare ogni sentiero meno allettante, quindi si può solo tracciare una linea e passare a qualcosa di più importante martellare / complicato / molto utilizzato.

A volte non è sufficiente per provare solo l'API pubblica perché una certa logica particolarmente interessante è in agguato dentro, ed è eccessivamente doloroso per impostare il sistema in moto ed esercitare tali percorsi particolari. Questo è quando si verifica il coraggio di esso non pagare.

In questi giorni, ho tendono a scrivere numerose classi semplici, (spesso estremamente) che fanno cime una o due cose. Sono quindi implementare il comportamento desiderato delegando tutte le funzionalità complicata a tali classi interne. Cioè Ho interazioni un po 'più complesso, ma le classi molto semplici.

se cambio implementazione e hanno il refactoring alcune di queste classi, di solito non mi interessa. Ho tenere i miei test coibentate come meglio posso, quindi è spesso una semplice modifica per farli lavorare di nuovo. Tuttavia, se I fare devono gettare alcune delle classi interne di distanza, spesso mi sostituisco una manciata di classi e scrivo alcuni del tutto nuovi test, invece. Sento spesso persone che si lamentano di dover tenere le prove fino ad oggi dopo il refactoring e, mentre è a volte inevitabile e faticoso, se il livello di granularità è abbastanza bene, di solito non è un grosso problema per buttare via un codice + test.

Sento che questo è uno dei principali differenze tra la progettazione per la testabilità e non dà fastidio.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top