Het tweede artikel in deze korte serie leert je hoe je verschillende matchers gebruikt die bij RSpec worden geleverd. Het laat ook zien hoe u uw testsuite kunt versnipperen door middel van tagging, hoe callbacks werken en hoe u bepaalde gegevens kunt extraheren. We breiden een beetje uit van de basisoverlevingskit uit het eerste artikel en laten je genoeg zien om zonder teveel touw gevaarlijk te zijn om jezelf op te hangen.
In het eerste artikel hebben we behoorlijk veel tijd besteed aan het proberen te beantwoorden van het "waarom?" Testen. Ik stel voor dat we meteen teruggaan naar het "hoe?" En onszelf meer context besparen. We hebben dat deel al uitgebreid besproken. Laten we eens kijken wat RSpec nog meer te bieden heeft dat jij als beginner meteen kunt doen.
Dus dit gaat het hart der dingen naderen. RSpec biedt u een massa zogenaamde matchers. Dit zijn jouw brood en boter wanneer je je verwachtingen schrijft. Tot nu toe heb je het gezien .tot eq
en .not_to eq
. Maar er is een veel groter arsenaal om je specificaties te schrijven. Je kunt testen om fouten te maken, voor waarheidsgetrouwe en valse waarden, of zelfs voor specifieke klassen. Laten we een paar opties uitvoeren om aan de slag te gaan:
.tot eq
.not_to eq
Dit test op gelijkwaardigheid.
... het is een 'slimme beschrijving' die je verwacht (agent.enemy) .om 'Ernst Stavro Blofeld' te verwachten (agent.enemy) .niet om 'Winnie Pooh' te eindigen ...
Om de zaken kort te houden, heb ik twee verwachtingsverklaringen in één verpakt het
blok. Het is echter een goede gewoonte om slechts één ding per test te testen. Hierdoor blijven de dingen veel gerichter en worden uw tests minder broos als u dingen verandert.
.to be_truthy
.om eerlijk te zijn
... het is een 'slimme beschrijving' die je verwacht (agent.hero?). Om te verwachten dat ... (vijand.megalomaniac?) Echt waar is ...
Het verschil is dat be_truthy
is waar als het dat niet is nul
of vals
. Dus het zal slagen als het resultaat geen van deze twee soorten "waarachtig" is. .om eerlijk te zijn
aan de andere kant accepteert alleen een waarde die dat wel is waar
en niets anders.
.te zijn_falsy
.om vals te zijn
... het is een 'slimme beschrijving' die je verwacht (agent.coward?) Om te verwachten dat valsheid (vijand.megalomaniac?) Een vals einde is ...
Vergelijkbaar met de twee bovenstaande voorbeelden, .te zijn_falsy
verwacht een van beide vals
of a nul
waarde, en .om vals te zijn
zal alleen een directe vergelijking doen op vals
.
.te worden
.to_not be_nil
En last but not least, dit test precies voor nul
zelf. Ik bespaar je het voorbeeld.
.matchen ()
Ik hoop dat je al het genoegen hebt gehad om naar reguliere expressies te kijken. Als dit niet het geval is, is dit een reeks tekens waarmee u een patroon kunt definiëren dat u tussen twee schuine strepen plaatst om naar tekenreeksen te zoeken. Een regex kan erg handig zijn als u op zoek bent naar bredere patronen die u in zo'n uitdrukking kunt generaliseren.
... het is een 'slimme beschrijving' die je verwacht (agent.number.to_i). Om te matchen (/ \ d 3 /) einde ...
Stel dat we te maken hebben met agenten zoals James Bond, 007, die driecijferige nummers krijgen toegewezen. Dan zouden we het op deze manier kunnen testen - primitief hier natuurlijk.
>
<
<=
> =
Vergelijkingen komen vaker van pas dan men zou denken. Ik neem aan dat de onderstaande voorbeelden betrekking hebben op wat u moet weten.
... het is een 'slimme beschrijving' ... verwacht (agentnummer) < quartermaster.number expect(agent.number).to be > m.number verwacht (agent.kill_count) .om> = 25 te zijn (quartermaster.number_of_gadgets) .om te zijn <= 5 end…
Nu worden we ergens minder saai. U kunt ook testen op klassen en typen:
.to be_an_instance_of
.een ... zijn
.te zijn_an
... het is een 'slimme beschrijving' doen missie = Mission.create (naam: 'Moonraker') agent = Agent.create (naam: 'James Bond') mission.agents << agent expect(@mission.agents).not_to be_an_instance_of(Agent) expect(@mission.agents).to be_a(ActiveRecord::Associations::CollectionProxy) end…
In het dummy-voorbeeld hierboven kunt u zien dat een lijst met agenten die aan een missie zijn gekoppeld, niet van klasse is Middel
maar van ActiveRecord :: Verenigingen :: CollectionProxy
. Wat je hiervan moet afnemen is dat we gemakkelijk zelf voor lessen kunnen testen terwijl we zeer expressief blijven. .een ... zijn
en .te zijn_an
doe een en hetzelfde. Je hebt beide opties beschikbaar om dingen leesbaar te houden.
Testen op fouten is ook enorm handig in RSpec. Als je super fris bent voor Rails en nog niet zeker weet welke fouten het framework je kan opleveren, dan heb je misschien niet de behoefte om deze te gebruiken - dat is logisch. In een later stadium van je ontwikkeling zul je ze echter erg handig vinden. Je hebt vier manieren om met ze om te gaan:
.te verhogen_fout
Dit is de meest generieke manier. Welke fout dan ook wordt gemaakt, wordt in uw net geworpen.
.to raise_error (ErrorClass)
Op die manier kun je precies aangeven uit welke klasse de fout moet komen.
.to raise_error (ErrorClass, "Some error message")
Dit is nog fijner omdat je niet alleen de klasse van de fout noemt maar ook een specifieke boodschap die met de fout moet worden weggegooid.
.to raise_error ("Enkele foutmelding)
Of u noemt de foutmelding zelf zonder de foutklasse. Het verwachtingsgedeelte moet echter een beetje anders worden geschreven - we moeten het gedeelte onder de tekst in een codeblok zelf wikkelen:
... het is een 'slimme beschrijving' do agent = Agent.create (naam: 'James Bond') verwacht agent.lady_killer?. Om raise_error (NoMethodError) te verwachten double_agent.name .to raise_error (NameError) verwacht double_agent. name .to raise_error ("Fout: geen dubbele agents rond") verwachten double_agent.name .to raise_error (NameError, "Error: No double agents around") end ...
.om te beginnen met
.om te eindigen met
Omdat we vaak omgaan met collecties bij het bouwen van web-apps, is het fijn om een tool te hebben om er in te kijken. Hier hebben we twee agenten toegevoegd, Q en James Bond, en wilden alleen weten wie als eerste en als laatste komt in de verzameling agenten voor een bepaalde missie - hier Moonraker.
... het is een 'slimme beschrijving' do moonraker = Mission.create (naam: 'Moonraker') bond = Agent.create (naam: 'James Bond') q = Agent.create (name: 'Q') moonraker.agents << bond moonraker.agents << q expect(moonraker.agents).to start_with(bond) expect(moonraker.agents).to end_with(q) end…
.opnemen
Deze is ook handig om de inhoud van collecties te controleren.
... het is een 'slimme beschrijving' doen missie = Mission.create (naam: 'Moonraker') bond = Agent.create (naam: 'James Bond') mission.agents << bond expect(mission.agents).to include(bond) end…
Deze predicaat-matchers zijn een functie van RSpec om op dynamische wijze matchers voor u te creëren. Als u in uw modellen predikaatmethoden hebt, bijvoorbeeld (eindigend met een vraagteken), weet RSpec dat het matchers voor u moet maken die u in uw tests kunt gebruiken. In het onderstaande voorbeeld willen we testen of een agent James Bond is:
class Agent < ActiveRecord::Base def bond? name == 'James Bond' && number == '007' && gambler == true end… end
Nu kunnen we dit als volgt in onze specificaties gebruiken:
... het is een 'slimme beschrijving' do agent = Agent.create (naam: 'James Bond', nummer: '007', gokker: waar) verwacht (agent) .to be_bond eindig 'een slimme beschrijving' do agent = Agent. create (naam: 'James Bond') verwacht (agent). not_to be_bond einde ...
RSpec laat ons de methode gebruiken zonder het vraagteken - om een betere syntaxis te vormen, neem ik aan. Cool, is het niet?
laat
en laat!
lijkt in eerste instantie op variabelen, maar het zijn in feite hulpmethoden. De eerste wordt lui geëvalueerd, wat betekent dat het alleen wordt uitgevoerd en geëvalueerd wanneer een spec het daadwerkelijk gebruikt, en de andere laat met de knal (!) Wordt uitgevoerd ongeacht of het door een spec wordt gebruikt of niet. Beide versies zijn gememoriseerd en hun waarden worden binnen hetzelfde bereik geplaatst in de cache.
beschrijf Mission, '#prepare',: let do let (: mission) Mission.create (name: 'Moonraker') let! (: bond) Agent.create (name: 'James Bond') het 'voegt toe agenten naar een missie 'do mission.prepare (bond) verwachten (mission.agents) .om het einde van de obligatie te omvatten
De knalversie die niet lui wordt geëvalueerd, kan tijdrovend en daarom kostbaar zijn als het je mooie nieuwe vriend wordt. Waarom? Omdat het deze gegevens voor elke test in kwestie zal opzetten, wat er ook gebeurt, en uiteindelijk een van deze vervelende dingen kan worden die uw testsuite aanzienlijk vertragen.
U zou deze functie van RSpec sindsdien moeten kennen laat
is algemeen bekend en wordt gebruikt. Dat gezegd hebbende, zal het volgende artikel enkele problemen met u laten zien waarvan u op de hoogte moet zijn. Gebruik deze hulpmethoden met voorzichtigheid, of op zijn minst in kleine doses voor nu.
RSpec biedt u de mogelijkheid om het te testen onderwerp heel expliciet te verklaren. Er zijn betere oplossingen hiervoor, en we zullen in het volgende artikel de nadelen van deze aanpak bespreken wanneer ik een paar dingen laat zien die je over het algemeen wilt vermijden. Maar laten we nu eens kijken naar wat onderwerpen
kan het voor je doen:
beschrijf Agent, '#status' onderwerp doen Agent.create (name: 'Bond') it 'retourneert de agentstatus' verwacht wel (subject.status) .niet 'eind' MIA zijn
Deze aanpak kan je enerzijds helpen bij het verminderen van codeduplicatie, waarbij een protagonist eenmaal in een bepaalde scope wordt verklaard, maar het kan ook leiden tot iets dat een mystery guest wordt genoemd. Dit betekent eenvoudigweg dat we in een situatie terechtkomen waarin we gegevens gebruiken voor een van onze testscenario's, maar we hebben geen idee meer waar het daadwerkelijk vandaan komt en waar het uit bestaat. Meer hierover in het volgende artikel.
Als je nog geen callbacks kent, wil ik je even een korte toelichting geven. Terugbelverzoeken worden uitgevoerd op bepaalde specifieke punten in de levenscyclus van code. In termen van Rails zou dit betekenen dat je code hebt die wordt uitgevoerd voordat objecten worden gemaakt, bijgewerkt, vernietigd, enz.
In de context van RSpec is dit de levenscyclus van tests die worden uitgevoerd. Dat betekent gewoon dat u haken kunt opgeven die moeten worden uitgevoerd voordat of nadat elke test wordt uitgevoerd in het spec-bestand, bijvoorbeeld, of gewoon rond elke test. Er zijn nog enkele meer verfijnde opties beschikbaar, maar ik raad aan om voorlopig niet in de details verdwaald te raken. Eerste dingen eerst:
voor (: elk)
Deze callback wordt vóór elk testvoorbeeld uitgevoerd.
beschrijf Agent, '#favorite_gadget' do before (: each) do @gagdet = Gadget.create (naam: 'Walther PPK') end it 'retourneert een item, het favoriete gadget van de agent' do agent = Agent.create (naam : 'James Bond') agent.favorite_gadgets << @gadget expect(agent.favorite_gadget).to eq 'Walther PPK' end… end
Stel dat u een bepaald gadget nodig heeft voor elke test die u in een bepaald bereik uitvoert. voor
laat je dit in een blok uitpakken en klaarmaakt dit kleine fragment handig voor je op. Wanneer u gegevens op die manier instelt, moet u bijvoorbeeld instantievariabelen gebruiken om toegang te hebben tot verschillende scopes.
Laat je niet voor de gek houden door dit voorbeeld. Alleen omdat je dit soort dingen kunt doen, betekent nog niet dat je het zou moeten doen. Ik wil voorkomen dat ik AntiPattern-territorium binnenloop en je in de war raak, maar aan de andere kant wil ik ook de nadelen van deze eenvoudige dummy-oefeningen een beetje uitleggen..
In het bovenstaande voorbeeld zou het veel expressiever zijn als u de benodigde objecten op basis van tests test. Vooral bij grotere spec-bestanden kun je deze kleine verbindingen snel uit het oog verliezen en het voor anderen moeilijker maken om deze puzzels samen te stellen.
voor alles)
Deze voor
blok wordt slechts eenmaal uitgevoerd vóór alle andere voorbeelden in een spec-bestand.
beschrijf Agent, '#enemy' do before (: all) do @main_villain = Villain.create (naam: 'Ernst Stavro Blofeld') @mission = Mission.create (name: 'Moonraker') @ mission.villains << @main_villain end it 'returns the main enemy Bond has to face in his mission' do agent = Agent.create(name: 'James Bond') @mission.agents << agent expect(agent.enemy).to eq 'Ernst Stavro Blofeld' end… end
Wanneer je de vier fasen van de test onthoudt, voor
blokken zijn soms nuttig om iets voor je in te stellen dat regelmatig moet worden herhaald - waarschijnlijk dingen die een beetje meer meta in de natuur zijn.
na elke)
en ten slotte)
hebben hetzelfde gedrag maar worden gewoon uitgevoerd nadat uw tests zijn uitgevoerd. na
wordt vaak gebruikt voor het opruimen van uw bestanden, bijvoorbeeld. Maar ik denk dat het een beetje vroeg is om dat aan te pakken. Dus leg het vast in het geheugen, weet dat het er is voor het geval je het nodig hebt, en laten we verdergaan met het verkennen van andere, meer basale zaken.
Al deze callbacks kunnen strategisch worden geplaatst om aan uw behoeften te voldoen. Plaats ze in beschrijven
blokkeer scope die u nodig hebt om ze uit te voeren - ze hoeven niet noodzakelijkerwijs bovenop uw spec-bestand te worden geplaatst. Ze kunnen eenvoudig in de specificaties worden genest.
beschrijf Agent do before (: each) do @mission = Mission.create (naam: 'Moonraker') @bond = Agent.create (naam: 'James Bond', nummer: '007') end beschrijven '#enemy' doen vóór (: each) do @main_villain = Villain.create (naam: 'Ernst Stavro Blofeld') @ mission.villains << @main_villain end describe 'Double 0 Agent with associated mission' do it 'returns the main enemy the agent has to face in his mission' do @mission.agents << @bond expect(@bond.enemy).to eq 'Ernst Stavro Blofeld' end end describe 'Low-level agent with associated mission' do it 'returns no info about the main villain involved' do some_schmuck = Agent.create(name: 'Some schmuck', number: '1024') @mission.agents << some_schmuck expect(some_schmuck.enemy).to eq 'That's above your paygrade!' end end… end end
Zoals je kunt zien, kun je callback-blokken op elk gewenst bereik naar eigen inzicht plaatsen en zo diep gaan als je nodig hebt. De code in de callback wordt uitgevoerd binnen het bereik van elk beschreven blokbereik. Maar een klein beetje advies: als je de behoefte voelt om te veel te nestelen en dingen een beetje rommelig en ingewikkeld lijken, heroverweeg dan je aanpak en overweeg hoe je de tests en hun opstelling kunt vereenvoudigen. KUS! Houd het simpel, stom. Let ook op hoe mooi dit leest wanneer we deze tests forceren om te falen:
Fouten: 1) Agent # vijand Double 0 Agent met bijbehorende missie retourneert de hoofdvijand die de agent in zijn missie onder ogen moet zien Falen / Fout: verwacht (@ bond.enemy) .to eq 'Ernst Stavro Blofeld' verwacht: "Ernst Stavro Blofeld "got:" Blofeld "2) Agent # enemy Low-level agent met bijbehorende missie geeft geen info over de belangrijkste betrokken slechter Fout / Fout: verwacht (some_schmuck.enemy) .to eq 'Dat is boven uw paygrade!' verwacht: "Dat is boven uw paygrade!" heb: "Blofeld"
Laten we ook snel bekijken welke generators door RSpec voor u worden geleverd. Je hebt er al een gezien toen we hem gebruikten rails genereren van rspec: installeren
. Deze kleine kerel maakte het opzetten van RSpec voor ons snel en gemakkelijk. Wat hebben we nog meer??
rspec: model
Wil je nog een dummy modelspecificatie hebben?
rails genereren rspec: model another_dummy_model
maak specificatie / modellen / another_dummy_model_spec.rb
Snel, is het niet? Of een nieuwe specificatie voor een controllertoets, bijvoorbeeld:
rspec: controller
rails genereren rspec: controller dummy_controller
spec / controllers / dummy_controller_controller_spec.rb
rspec: view
Hetzelfde werkt natuurlijk voor meningen. We zullen dergelijke meningen echter niet testen. Specs voor views geven je de minste waar voor je geld, en het is volstrekt voldoende in waarschijnlijk bijna elk scenario om indirect je mening te testen via functietests.
Functietests zijn geen specialiteit van RSpec per se en zijn meer geschikt voor een ander artikel. Dat gezegd hebbende, als je nieuwsgierig bent, kijk dan eens naar Capybara, wat een uitstekende tool is voor dat soort dingen. Hiermee kunt u hele flows testen waarbij meerdere delen van uw app samenkomen, waarbij u volledige functies test terwijl u de browserervaring simuleert. Bijvoorbeeld een gebruiker die voor meerdere artikelen in een winkelwagentje betaalt.
rspec: helper
Met dezelfde generatorstrategie kunnen we ook zonder veel gedoe een helper plaatsen.
rails genereren rspec: helper dummy_helper
create spec / helpers / dummy_helper_helper_spec.rb
Het dubbele helper_helper
een deel was geen ongeluk. Wanneer we het een meer "zinvolle" naam geven, zul je zien dat RSpec net hecht _helper
op zichzelf.
rails genereren rspec: helper important_stuff
create spec / helpers / important_stuff_helper_spec.rb
Nee, deze map is geen plaats om je kostbare hulpmethoden op te halen die naar voren komen tijdens het refactoren van je tests. Deze zouden onder gaan spec / support
, werkelijk. spec / helpers
is voor de tests die je zou moeten schrijven voor je kijk helpers-een helper zoals set_date
zou een algemeen voorbeeld zijn. Ja, volledige testverslagen van uw code moeten ook deze hulpmethoden bevatten. Gewoon omdat ze vaak klein en triviaal lijken, betekent niet dat we ze over het hoofd moeten zien of hun potentieel voor bugs die we willen vangen negeren. Hoe complexer de helper ook daadwerkelijk wordt, des te meer reden waarom je zou moeten schrijven helper_spec
ervoor!
Voor het geval je er meteen mee gaat spelen, onthoud dat je je helper-methoden moet uitvoeren op a helper
object wanneer u uw helpertests schrijft om te kunnen werken. Dus ze kunnen alleen worden blootgesteld met behulp van dit object. Iets zoals dit:
beschrijf '#set_date' do ... helper.set_date ... end ...
U kunt hetzelfde soort generators gebruiken voor functie-specificaties, integratiespecificaties en specificaties voor mailers. Deze vallen buiten ons bereik voor vandaag, maar u kunt ze in het geheugen vastleggen voor toekomstig gebruik:
De specificaties die we via de bovenstaande generator hebben gemaakt, zijn klaar voor gebruik en u kunt uw tests meteen toevoegen. Laten we echter een klein beetje kijken naar een verschil tussen specificaties:
vereisen 'rails_helper' RSpec.describe DummyModel, type:: model doen in behandeling "voeg enkele voorbeelden toe aan (of verwijder) # __ FILE__" einde
vereisen 'rails_helper' RSpec.describe DummyControllerController, type:: controller do end
vereisen 'rails_helper' RSpec.describe DummyHelperHelper, type:: helper doen in behandeling "voeg enkele voorbeelden toe aan (of verwijder) # __ FILE__" einde
Het heeft geen wonderkind nodig om erachter te komen dat ze allemaal verschillende typen hebben. Deze :type
RSpec-metadata biedt u de mogelijkheid om uw tests in verschillende bestandsstructuren in te delen en te versnipperen. Je kunt deze testen op die manier een beetje beter richten. Stel dat je een soort helpers wilt die alleen zijn geladen voor controller-specificaties, bijvoorbeeld. Een ander voorbeeld is dat u een andere directorystructuur wilt gebruiken voor specificaties die RSpec niet verwacht. Het hebben van deze metadata in uw tests maakt het mogelijk om RSpec-ondersteuningsfuncties te blijven gebruiken en niet om de testsuite te doorbreken. Dus je bent vrij om elke mapstructuur die voor je werkt te gebruiken als je dit toevoegt :type
metadata.
Uw standaard RSpec-tests zijn echter niet afhankelijk van die metadata. Wanneer u deze generators gebruikt, worden ze gratis toegevoegd, maar u kunt ze ook volledig vermijden als u ze niet nodig hebt.
U kunt deze metadata ook gebruiken om in uw specificaties te filteren. Stel dat u een eerder blok hebt dat alleen op modelspecificaties zou moeten draaien, bijvoorbeeld. Netjes! Voor grotere testsuites kan dit op een dag erg handig zijn. U kunt filteren welke gefocuste groep tests u wilt uitvoeren in plaats van de hele suite uit te voeren, wat even kan duren.
Uw opties gaan natuurlijk verder dan de drie bovenstaande tagopties. Laten we meer te weten komen over het in de volgende sectie slicen en in stukken snijden van uw tests.
Wanneer u na verloop van tijd een groter testsuite vergaart, is het niet alleen voldoende om tests in bepaalde mappen uit te voeren om RSpec-tests snel en efficiënt uit te voeren. Wat u wilt kunnen doen is testen uitvoeren die bij elkaar horen, maar mogelijk verspreid zijn over meerdere mappen. Tagging om te redden! Begrijp me niet verkeerd, het is ook belangrijk om je tests slim in je mappen te organiseren, maar het taggen neemt dit net iets verder op.
U geeft uw tests enkele metadata als symbolen zoals ": wip", ": checkout", of wat u maar wilt. Wanneer u deze gerichte testgroepen uitvoert, geeft u eenvoudig aan dat RSpec deze keer andere tests moet negeren door een vlag met de naam van de tags op te geven.
beschrijf Agent,: wip do it 'is nu een puinhoop' verwacht (agent.favorite_gadgets) .to eq 'onbekend' end end
rspec --tag wip
Mislukkingen: 1) Agent is nu een puinhoop Fout / Fout: verwacht (agent.favorite_gadgets) .to 'Onbekend' ...
U kunt ook allerlei tests uitvoeren en een groep groepen negeren die op een bepaalde manier zijn getagd. Geef gewoon een tilde (~) voor de tagnaam op, en RSpec negeert deze tests graag.
rspec --tag ~ wip
Meerdere tags synchroon uitvoeren is ook geen probleem:
rspec --tag wip --tag checkout rspec --tag ~ wip --tag checkout
Zoals je hierboven kunt zien, kun je ze naar believen mixen en matchen. De syntaxis is niet perfect-herhalend --label
is misschien niet ideaal - maar hey, het is ook geen biggie! Ja, dit alles is wat extra werk en mentale overhead als je de specificaties samenstelt, maar aan de andere kant biedt het je echt een krachtig vermogen om je testsuite op aanvraag in te delen. Bij grotere projecten kan het u op die manier een hoop tijd besparen.
Wat je tot nu toe hebt geleerd, moet je voorzien van de absolute basis om zelf tests te spelen - een overlevingspakket voor beginners. En speel en maak zoveel mogelijk fouten. Neem RSpec en het hele testgedreven dingetje mee en verwacht niet meteen kwaliteitstests te schrijven. Er ontbreken nog een aantal stukjes voordat je je comfortabel voelt en voordat je er effectief mee aan de slag gaat.
Voor mij was dit in het begin een beetje frustrerend omdat het moeilijk was om te zien hoe iets te testen toen ik het nog niet had geïmplementeerd en niet volledig begreep hoe het zich zou gedragen.
Testen bewijst echt of u een raamwerk zoals Rails begrijpt en weet hoe de onderdelen in elkaar passen. Wanneer u tests schrijft, moet u verwachtingen kunnen schrijven over hoe een framework zich zou moeten gedragen.
Dat is niet gemakkelijk als je net begint met dit alles. Omgaan met meerdere domeinspecifieke talen, zoals RSpec en Rails, en het leren van de Ruby API kan verwarrend zijn. Voel je niet slecht als de leercurve ontmoedigend lijkt; het wordt gemakkelijker als je eraan vasthoudt. Het laten branden van deze gloeilamp zal niet gebeuren in de nacht, maar voor mij was het de moeite waard.