Test Driven Development: un modo valido / accettato per testare le operazioni del file system?


14

Attualmente sto lavorando a un progetto che genera una tabella (tra le altre cose) basata sul contenuto di un file system e, a sua volta, apporta alcune modifiche ai metadati sulle cose che trova. La domanda è: come devono essere scritti o impostati i test? C'è un modo semplice per deridere questo? O devo installare un "sandbox"?

Risposte:


13

Come sempre in TDD con risorse esterne: crei una o più interfacce per le operazioni del tuo filesystem e le "deridi". Vuoi testare il tuo "generatore di tabelle" e il codice di modifica dei metadati, non le operazioni del file system stesso (molto probabilmente stai usando implementazioni di librerie già pronte per accedere al file system).


TDD sconsiglia di deridere l'implementazione dell'unità in prova. Vedi (e, g) solnic.eu/2014/05/22/mocking-and-ruby.html
soru

1
@soru: Non è quello che raccomanda questa risposta. Si consiglia di creare prima le interfacce, quindi deridere l' interfaccia . Quindi testate la logica aziendale, ma non l'interfaccia del filesystem.
sleske,

5
Ma sembra che la logica aziendale sia definita in termini di file e directory. Quindi le cose che chiamano l'API del file system sono quelle che devono essere testate. Il test di qualsiasi logica aziendale non file correlata non necessita di derisione; provalo e basta.
soru,

@soru giusto, quindi crei un sottile strato attorno a file e cartelle con un'interfaccia definita in modo tale che tutte le operazioni specifiche del dominio siano sul lato client e il lato dell'implementazione sia abbastanza banale da essere sicuro che funzioni senza test unitari (i test di integrazione essere ancora richiesto). Simile all'idea di una Humble Dialog nel codice dell'interfaccia utente o dell'utilizzo di un finto repository nel codice che opera su oggetti persistenti.
Jules,

2
Così efficacemente ci dimettiamo dal test della classe reale che interagisce con il file system, il database, ecc ... invece creiamo un'implementazione diversa con la stessa interfaccia di un mock / stub, ma la classe effettiva che lasciamo senza alcun tipo di test unitario, perché riteniamo che non possiamo testarlo unitamente e dovremmo invece eseguire test di integrazione per testarlo. È corretto?
Andrew Savinykh,

11

Cosa c'è di sbagliato nell'avere un file system "test"?

Creare una struttura di cartelle / directory modello con contenuto sufficiente per testare le operazioni.

Durante l'installazione del test unitario copia questa struttura iniziale (ti consigliamo di comprimere il modello e decomprimerlo nell'area di test). Esegui i tuoi test. Elimina tutto durante lo smontaggio.

Il problema del deridere è in primo luogo che i file system, i sistemi operativi e i database che appartengono al tuo progetto non si qualificano davvero come risorse esterne e in secondo luogo deridere le chiamate di sistema di basso livello è sia dispendioso in termini di tempo che soggetto a errori.


5
Deridere le operazioni del file system creerà test di esecuzione molto più rapidi rispetto all'utilizzo di un vero file system e se questo è più "soggetto a errori" è discutibile, direi che dipende dall'implementazione. Tuttavia, penso che il tuo suggerimento vada bene per la creazione di test di integrazione automatizzata (che in genere farei prima quando non faccio TDD). Ma l'OP ha chiesto espressamente TDD e i test delle unità TDD devono essere veloci.
Doc Brown,

1
Penso che i file system, se derisi, idealmente dovrebbero avere un'intera API scritta e gestita da alcuni gruppi, perché stai reinventando la ruota, se fai qualcosa di significativo con il file system.
Frank Hileman,

2
@Doc Brown - Sto presumendo che voglia fare operazioni dir, cancellare e rinominare le operazioni di tipo, tutte con casi limite che sarebbero dolorosi da deridere. Anche su hardware moderno decomprimere alcuni piccoli file in una directory è solo un po 'più lento del caricamento di una classe Java - dopo tutto è tutto IO.
James Anderson,

Più penso al file system di prova, più mi piace.
Frank Hileman,

3

Questo è il genere di cose di cui hai sicuramente bisogno per testare l'integrazione, poiché i file system del mondo reale hanno tutti i tipi di comportamenti strani (come il modo in cui Windows non consentirà di eliminare un file se qualche processo, incluso il deleter, lo ha aperto).

Quindi l'approccio TDD è di scrivere prima il test di integrazione (TDD, in senso stretto, non ha concetti distinti di "unit test" e "test di integrazione"; sono solo test). Molto probabilmente sarà sufficiente; quindi lavoro fatto, fermati, vai a casa .

In caso contrario, ci sarà una certa complessità interna che non è facile da testare adeguatamente organizzando i file. In tal caso, è sufficiente eliminare quella complessità, inserirla in una classe e scrivere test unitari per quella classe . Molto probabilmente scoprirai che quella classe comune è utilizzabile anche nei casi di database, file xml, ecc.

In nessun caso prenderesti il ​​nucleo fondamentale del codice che stai scrivendo e lo "deriderei" per scrivere test che passeranno se l'unità sotto test è sbagliata o meno.


Questa risposta mi ha davvero messo in prospettiva - 'unit test' and 'integration test'; they are just tests.penso realisticamente, questa sarà la soluzione migliore per il mio caso - Ho davvero bisogno di testare le librerie del file system che sto usando per i casi limite e come l'applicazione dovrebbe rispondere quelli. Se passo a una libreria di file system diversa, non voglio riscrivere un sacco di mock / codice di test per lavorare con la nuova libreria, ma avere una struttura di cartelle di prova e test di integrazione renderebbe molto più semplice.
1919

2

Comprendo la tua domanda come "Un modo valido / accettato per testare una classe che dipende dalle operazioni del file system". Non presumo che tu voglia testare il filesystem del tuo sistema operativo.

Al fine di mantenere lo sforzo di "interfacce per le operazioni del tuo filesystem e" deriderle "come suggerito dalla risposta di @Doc Brown il più piccolo possibile, è una buona idea usare flussi binari java o un lettore di testo (o equivalente in c # o il linguaggio di programmazione che stai usando) invece di usare File con nomi di file direttamente nella tua classe sviluppata da tdd.

Esempio:

Usando java ho implementato una classe CsvReader

public class CsvReader {
    private Reader reader;

    public CsvReader(Reader reader) {
        this.reader = reader;
    }
}

Per i test ho usato dati di memoria come questo

String contentOfCsv = "TestColumn1;TestColumn2\n"+
    "value1;value2\n";

CsvReader sut = new CsvReader(java.io.StringReader(contentOfCsv));

o incorporare testdata nelle risorse

CsvReader sut = new CsvReader(getClass().getResourceAsStream("/data.csv"));

In produzione utilizzo il file system

CsvReader sut = new CsvReader(new BufferedReader( new FileReader( "/import/Prices.csv" ) ));

In questo modo il mio CsvReader non dipende dal filesystem ma da un'astrazione "Reader" dove esiste un'implementazione per il filesystem.


2
L'unico problema qui è che l'OP non stava parlando delle operazioni sui file, ma delle operazioni sul file system e delle operazioni sui metadati - immagino che intendesse qualcosa come elencare tutti i file in una directory, aggiornare alcune informazioni EXIF ​​in tutti i file di immagini, ecc.
Doc Brown

Questo è corretto.
Kirbinator,

1
È possibile creare IDirectoryLister che ha un metodo String [] List (directory String); quindi FakeDirectoryLister può implementare quel metodo semplicemente restituendo il nuovo String [] {".", "..", "foo.bat", "bar.exe"};
Anders Lindén il

0

Creare un wrapper per le operazioni del file system. Nei test, passare un mock che implementa la stessa interfaccia del wrapper. In produzione, passare la confezione.

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.