Context-aware Reader

Je kunt alle code uit dit hoofdstuk hier vinden

In dit hoofdstuk wordt gedemonstreerd hoe je een contextbewuste io.Reader kunt testen, zoals geschreven door Mat Ryer en David Hernandez in The Pace Dev Blog.

Contextbewuste lezer?

Allereerst een korte introductie tot io.Reader.

Als je andere hoofdstukken in dit boek hebt gelezen, ben je io.Reader vast wel eens tegengekomen bij het openen van bestanden, het coderen van JSON en diverse andere veelvoorkomende taken. Het is een simpele abstractie van het lezen van data uit iets

type Reader interface {
	Read(p []byte) (n int, err error)
}

Door io.Reader te gebruiken, kun je veel hergebruik uit de standaardbibliotheek halen; het is een veelgebruikte abstractie (samen met zijn tegenhanger io.Writer).

Contextbewust?

In een eerder hoofdstuk hebben we besproken hoe we context kunnen gebruiken om te annuleren. Dit is vooral handig als je taken uitvoert die rekenintensief kunnen zijn en je ze wilt kunnen stoppen.

Als je een io.Reader gebruikt, heb je geen garanties over de snelheid; het kan 1 nanoseconde of honderden uren duren. Het kan handig zijn om dit soort taken in je eigen applicatie te kunnen annuleren, en dat is waar Mat en David over schreven.

Ze combineerden twee eenvoudige abstracties (context.Context en io.Reader) om dit probleem op te lossen.

Laten we proberen wat functionaliteit te TDD'en, zodat we een io.Reader zo kunnen inpakken dat deze geannuleerd kan worden.

Het testen hiervan vormt een interessante uitdaging. Normaal gesproken geef je bij het gebruik van een io.Reader deze meestal door aan een andere functie en houd je je niet echt bezig met de details, zoals json.NewDecoder of io.ReadAll.

Wat we willen demonstreren is zoiets als:

Gegeven een io.Reader met "ABCDEF", wanneer ik halverwege een annulatiesignaal stuur, wanneer ik probeer verder te lezen, krijg ik niets anders, dus krijg ik alleen "ABC".

Laten we de interface nog eens bekijken.

De Read-methode van de Reader leest de inhoud ervan in een []byte die we opgeven.

In plaats van alles te lezen, kunnen we dus:

  • Een byte-array met vaste grootte opgeven die niet alle inhoud bevat

  • Een annulatiesignaal verzenden

  • Probeer het opnieuw te lezen. Dit zou een foutmelding moeten opleveren met 0 gelezen bytes

Laten we nu gewoon een "happy path"-test schrijven zonder annulering, zodat we vertrouwd kunnen raken met het probleem zonder dat we al productiecode hoeven te schrijven.

  • Maak een io.Reader van een string met wat data.

  • Een byte-array om in te lezen die kleiner is dan de inhoud van de reader.

  • Roep read aan, controleer de inhoud en herhaal.

Hieruit kunnen we ons voorstellen dat er vóór de tweede leesbewerking een soort annulatiesignaal wordt verzonden om het gedrag te veranderen.

Nu we hebben gezien hoe het werkt, zullen we de rest van de functionaliteit TDD'en.

Schrijf eerst de test

We willen een io.Reader kunnen samenstellen met een context.Context.

Met TDD is het het beste om te beginnen met het bedenken van de gewenste API en er een test voor te schrijven.

Laat de compiler en de mislukte testuitvoer ons vervolgens naar een oplossing leiden

Probeer de test uit te voeren

Schrijf de minimale hoeveelheid code voor de test om uit te voeren en controleer de uitvoer van de mislukte test.

We moeten deze functie definiëren en deze moet een io.Reader retourneren.

Wanneer je dit probeert uit te voeren

Zoals verwacht

Schrijf voldoende code om het te laten slagen

Voorlopig retourneren we gewoon de io.Reader die we doorgeven

De test zou nu moeten slagen.

Ik weet het, ik weet het, dit klinkt misschien gek en pedant, maar voordat we aan de slag gaan, is het belangrijk dat we enige verificatie hebben dat we het "normale" gedrag van een io.Reader niet hebben verstoord. Deze test geeft ons vertrouwen voor de toekomst.

Schrijf eerst de test

Vervolgens moeten we proberen te annuleren.

We kunnen de eerste test min of meer kopiëren, maar nu:

  • We maken een context.Context met annulering, zodat we na de eerste keer lezen kunnen cancel.

  • Om onze code te laten werken, moeten we ctx aan onze functie doorgeven.

  • Vervolgens bevestigen we dat er na cancel niets is gelezen.

Probeer de test uit te voeren

Schrijf de minimale hoeveelheid code voor de test om uit te voeren en controleer de mislukte testuitvoer.

De compiler vertelt ons wat we moeten doen: onze handtekening bijwerken om een context te accepteren.

(Je moet de eerste test ook bijwerken om te slagen in context.Background)

Je zou nu een zeer duidelijke uitvoer van de mislukte test moeten zien

Schrijf voldoende code om het te laten slagen

Op dit punt is het kopiëren en plakken van het originele bericht van Mat en David, maar we gaan het rustig en iteratief aanpakken.

We weten dat we een type nodig hebben dat de io.Reader waaruit we lezen en de context.Context omvat, dus laten we dat aanmaken en proberen het vanuit onze functie te retourneren in plaats van de originele io.Reader

Zoals ik al vaker in dit boek heb benadrukt: ga langzaam te werk en laat de compiler je helpen

De abstractie voelt goed, maar implementeert niet de interface die we nodig hebben (io.Reader). Daarom voegen we de methode toe.

Voer de tests uit en ze zouden moeten compileren, maar paniek. Dit is nog steeds een stap in de goede richting.

Laten we de eerste test laten slagen door de aanroep te delegeren naar onze onderliggende io.Reader.

Op dit punt is onze 'happy path'-test opnieuw geslaagd en het voelt alsof we alles goed hebben geabstraheerd.

Om onze tweede test te laten slagen, moeten we de context.Context controleren om te zien of deze is geannuleerd.

Alle tests zouden nu moeten slagen. Je zult zien hoe we de fout van context.Context retourneren. Dit stelt aanroepers van de code in staat om de verschillende redenen voor de annulering te onderzoeken. Dit wordt uitgebreider behandeld in het oorspronkelijke bericht.

Samenvattend

  • Kleine interfaces zijn goed en zijn eenvoudig samen te stellen.

  • Wanneer je één ding (bijv. io.Reader) met een ander probeert uit te breiden, kun je meestal het delegatiepatroon gebruiken.

In software engineering is het delegatiepatroon een objectgeoriënteerd ontwerppatroon waarmee objectcompositie hetzelfde codehergebruik als overerving mogelijk maakt.

  • Een eenvoudige manier om met dit soort werk te beginnen, is door je gedelegeerde te omhullen en een test te schrijven die beweert dat deze zich gedraagt ​​zoals de gedelegeerde zich normaal gedraagt, voordat je andere onderdelen gaat samenstellen om het gedrag te wijzigen. Dit helpt je om ervoor te zorgen dat alles correct blijft werken terwijl je codeert naar je doel.

Laatst bijgewerkt