Unit testing kode med et filsystem avhengighet

stemmer
112

Jeg skriver en komponent som, gitt en ZIP-fil, må:

  1. Pakk ut filen.
  2. Finn en bestemt dll blant de utpakkede filene.
  3. Laster som dll gjennom refleksjon og påkalle en metode på den.

Jeg vil gjerne enheten test denne komponenten.

Jeg er fristet til å skrive kode som omhandler direkte med filsystemet:

void DoIt()
{
   Zip.Unzip(theZipFile, C:\\foo\\Unzipped);
   System.IO.File myDll = File.Open(C:\\foo\\Unzipped\\SuperSecret.bar);
   myDll.InvokeSomeSpecialMethod();
}

Men folk sier ofte: Ikke skrive enhet tester som er avhengige av filsystemet, database, nettverk, etc.

Hvis jeg skulle skrive dette i en enhet-test vennlig måte, jeg antar at det ville se slik ut:

void DoIt(IZipper zipper, IFileSystem fileSystem, IDllRunner runner)
{
   string path = zipper.Unzip(theZipFile);
   IFakeFile file = fileSystem.Open(path);
   runner.Run(file);
}

Jippi! Nå er det testes; Jeg kan mate inn test dobler (Mocks) til den DoIt metode. Men til hvilken pris? Jeg har nå hatt til å definere 3 nye grensesnitt bare for å gjøre dette testbar. Og hva, akkurat, jeg teste? Jeg tester at min DoIt fungere ordentlig samhandler med sine avhengigheter. Det trenger ikke teste det ut zip-filen ble pakket riktig, etc.

Det føles ikke som om jeg tester funksjonalitet lenger. Det føles som om jeg bare teste klasse interaksjoner.

Mitt spørsmål er dette : hva er den riktige måten å enheten test noe som er avhengig av filsystemet?

redigere Jeg bruker .NET, men konseptet kan gjelde Java eller egen kode også.

Publisert på 24/09/2008 klokken 16:46
kilden bruker
På andre språk...                            


11 svar

stemmer
6

En måte ville være å skrive unzip metoden for å ta InputStreams. Da enheten testen kunne konstruere en slik Input fra en byte array ved hjelp ByteArrayInputStream. Innholdet i byte array som kan være en konstant i enheten testkoden.

Svarte 24/09/2008 kl. 16:49
kilden bruker

stemmer
2

Forutsatt at "filsystemet samhandling" er godt testet i rammen selv, lage din metode for å jobbe med bekker, og teste dette. Åpne en Filestream og sendes til den metoden kan bli tatt ut av testene, som FileStream.Open er godt testet av ramme skapere.

Svarte 24/09/2008 kl. 16:50
kilden bruker

stemmer
22

Jeg er tilbakeholdne til å forurense min kode med typer og konsepter som eksisterer kun for å lette enhetstesting. Ja, hvis det gjør design renere og bedre da flott, men jeg tror det er ofte ikke tilfelle.

Min ta på dette er at enheten tester ville gjøre så mye som de kan som kanskje ikke er 100% dekning. Faktisk kan det bare være 10%. Poenget er, bør enheten testene være rask og har ingen eksterne avhengigheter. De kan teste saker som "denne metoden kaster en ArgumentNullException når du passerer i null for denne parameteren".

Jeg vil deretter legge til integrasjonstester (også automatiske og sannsynligvis ved hjelp av den samme enhet testing rammer) som kan ha avhengigheter og eksterne test ende-til-ende-scenarier som disse.

Ved måling av kodedekning, jeg måle både enhet og integrasjonstester.

Svarte 24/09/2008 kl. 16:51
kilden bruker

stemmer
1

Du bør ikke teste klasse interaksjon og funksjon ringer. i stedet bør du vurdere integrasjonstesting. Test ønsket resultat og ikke filen lasteoperasjonen.

Svarte 24/09/2008 kl. 16:52
kilden bruker

stemmer
8

Det er ingenting galt med å trykke på filsystemet, bare anser det som en integrasjonstest snarere enn en enhet test. Jeg vil bytte hardkodet banen med en relativ bane og lage en testdata undermappe som skal inneholde glidelåser for enheten testene.

Hvis integrasjonstester tar for lang tid å kjøre deretter skille dem ut slik at de ikke kjører så ofte som din raske enhet tester.

Jeg er enig, noen ganger tror jeg interaksjon basert testing kan føre til for mye kobling og ofte ender opp med ikke å gi nok verdi. Du virkelig ønsker å teste pakke ut filen her ikke bare bekrefte at du ringer de rette metodene.

Svarte 24/09/2008 kl. 16:57
kilden bruker

stemmer
3

Dette synes å være mer av en integrasjonstest som du er avhengig av en bestemt detalj (filsystemet) som kan endre, i teorien.

Jeg ville abstrakt koden som omhandler OS i sin egen modul (klasse, montering, krukke, uansett). I ditt tilfelle du ønsker å laste en bestemt DLL hvis det blir funnet, så gjør en IDllLoader grensesnitt og DllLoader klasse. Har din app erverve DLL fra DllLoader bruke grensesnitt og test det .. du er ikke ansvarlig for unzip kode afterall rett?

Svarte 24/09/2008 kl. 17:00
kilden bruker

stemmer
36

Det er egentlig ikke noe galt med dette, det er bare et spørsmål om du kaller det en enhet test eller en integrasjonstest. Du må bare sørge for at hvis du samhandler med filsystemet, er det ingen utilsiktede bivirkninger. Spesielt, sørg for at du rydde opp etter deg selv - slette midlertidige filer du har opprettet - og at du ikke utilsiktet overskrive en eksisterende fil som tilfeldigvis har samme filnavn som en midlertidig fil du brukte. Bruk alltid relative baner og ikke absolutte baner.

Det ville også være en god idé å chdir()i en midlertidig katalog før du kjører testen, og chdir()tilbake etterpå.

Svarte 24/09/2008 kl. 17:09
kilden bruker

stemmer
0

Som andre har sagt, er den første greit som en integrasjonstest. Den andre tester bare hva funksjonen er ment å faktisk gjøre, som er alt en enhet test bør gjøre.

Som vist, det andre eksempelet ser litt meningsløst, men det gir deg muligheten til å teste hvordan funksjonen reagerer på feil i noen av trinnene. Du trenger ikke ha noen feilkontroll i eksempelet, men i den virkelige systemet du kan ha, og avhengighet injeksjon vil la deg teste alle svar på eventuelle feil. Deretter vil kostnaden har vært verdt det.

Svarte 16/12/2008 kl. 11:52
kilden bruker

stemmer
1

For enhet test vil jeg foreslå at du tar testen filen i prosjektet (EAR-fil eller tilsvarende) og deretter bruke en relativ bane i enheten tester dvs. "../testdata/testfile".

Så lenge prosjektet er riktig eksporteres / importeres enn enheten testen skal fungere.

Svarte 16/12/2008 kl. 12:00
kilden bruker

stemmer
50

Jippi! Nå er det testes; Jeg kan mate inn test dobler (Mocks) til den DoIt metode. Men til hvilken pris? Jeg har nå hatt til å definere 3 nye grensesnitt bare for å gjøre dette testbar. Og hva, akkurat, jeg teste? Jeg tester at min DoIt fungere ordentlig samhandler med sine avhengigheter. Det trenger ikke teste det ut zip-filen ble pakket riktig, etc.

Du har truffet spikeren rett på hodet. Hva du ønsker å teste er logikken i metoden din, ikke nødvendigvis om en ekte fil kan tas opp. Det skal ikke teste (i denne enheten test) om en fil er riktig pakket, metoden tar det for gitt. Grensesnittene er verdifulle i seg selv, fordi de gir abstraksjoner som du kan programmere mot, snarere enn implisitt eller eksplisitt å stole på en konkret gjennomføring.

Svarte 02/11/2010 kl. 05:15
kilden bruker

stemmer
39

Ditt spørsmål avslører en av de vanskeligste delene av testing for utviklere bare å komme inn i den:

"Hva i helvete tester jeg?"

Ditt eksempel er ikke veldig interessant fordi det bare limer noen API-kall sammen, så hvis du skulle skrive en enhet test for det du vil ende opp med bare å hevde at metoder ble kalt. Tester som dette tett par dine gjennomføringen detaljer på prøve. Dette er dårlig fordi nå må du endre testen hver gang du endrer gjennomføringen detaljer av metoden fordi endring gjennomføringen detaljer bryter testen (e)!

Å ha dårlige tester er faktisk verre enn å ha ingen tester i det hele tatt.

I ditt eksempel:

void DoIt(IZipper zipper, IFileSystem fileSystem, IDllRunner runner)
{
   string path = zipper.Unzip(theZipFile);
   IFakeFile file = fileSystem.Open(path);
   runner.Run(file);
}

Selv om du kan passere i spotter, det er ingen logikk i metoden for å teste. Hvis du skulle forsøke en enhet test for dette kan det se omtrent slik ut:

// Assuming that zipper, fileSystem, and runner are mocks
void testDoIt()
{
  // mock behavior of the mock objects
  when(zipper.Unzip(any(File.class)).thenReturn("some path");
  when(fileSystem.Open("some path")).thenReturn(mock(IFakeFile.class));

  // run the test
  someObject.DoIt(zipper, fileSystem, runner);

  // verify things were called
  verify(zipper).Unzip(any(File.class));
  verify(fileSystem).Open("some path"));
  verify(runner).Run(file);
}

Gratulerer, du i utgangspunktet kopi limt gjennomføringen detaljer om DoIt()metoden i en test. Glad vedlikehold.

Når du skriver testene du ønsker å teste hva , og ikke hvordan . Se Black Box Testing for mer.

Den HVA er navnet på metoden din (eller i det minste det skal være). Den HVORDAN er alle de små gjennomføringen detaljer som lever inni metoden. Gode tester kan du bytte ut hvordan uten å bryte HVA .

Tenk på det på denne måten, spør deg selv:

"Hvis jeg endrer gjennomføringen detaljene i denne metoden (uten å endre kontraktene) vil det bryte min test (e)?"

Hvis svaret er ja, er du teste hvordan , og ikke hva .

For å svare på dine spørsmål om å teste kode med filsystemet avhengigheter, la oss si at du hadde noe litt mer interessant å gå videre med en fil, og du ønsket å lagre Base64 kodet innholdet i en byte[]til en fil. Du kan bruke strømmer for å teste at koden gjør det rette uten å sjekke hvordan den gjør det. Et eksempel kan være noe sånt som dette (i Java):

interface StreamFactory {
    OutputStream outStream();
    InputStream inStream();
}

class Base64FileWriter {
    public void write(byte[] contents, StreamFactory streamFactory) {
        OutputStream outputStream = streamFactory.outStream();
        outputStream.write(Base64.encodeBase64(contents));
    }
}

@Test
public void save_shouldBase64EncodeContents() {
    OutputStream outputStream = new ByteArrayOutputStream();
    StreamFactory streamFactory = mock(StreamFactory.class);
    when(streamFactory.outStream()).thenReturn(outputStream);

    // Run the method under test
    Base64FileWriter fileWriter = new Base64FileWriter();
    fileWriter.write("Man".getBytes(), streamFactory);

    // Assert we saved the base64 encoded contents
    assertThat(outputStream.toString()).isEqualTo("TWFu");
}

Testen bruker en ByteArrayOutputStream, men i søknaden (med avhengighet injeksjon) den virkelige StreamFactory (kanskje kalt FileStreamFactory) vil returnere FileOutputStreamfra outputStream()og ville skrive til en File.

Hva var interessant om writemetoden her er at det var å skrive innholdet ut Base64 kodet, så det er det vi testet for. For din DoIt()metode, dette ville være mer hensiktsmessig testet med en integrasjonstest .

Svarte 17/04/2014 kl. 02:52
kilden bruker

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more