Hoe ik test

In een recente discussie op Google+ zei een vriend van mij:Test-Driven Development (TDD) en Behavior-Driven Development (BDD) is Ivory Tower BS."Dit zette me ertoe aan na te denken over mijn eerste project, hoe ik me toen op dezelfde manier voelde en hoe ik me nu voel. Sinds dat eerste project heb ik een ritme van TDD / BDD ontwikkeld dat niet alleen voor mij werkt, maar ook ook voor de klant.

Ruby on Rails wordt geleverd met een testsuite, Test Unit genaamd, maar veel ontwikkelaars geven er de voorkeur aan RSpec, komkommer of een combinatie van beide te gebruiken. Persoonlijk geef ik de voorkeur aan het laatste, gebruikmakend van een combinatie van beide.


RSpec

Van de RSpec-site:

RSpec is een testtool voor de programmeertaal Ruby. Geboren onder de vlag van Gedraggedreven ontwikkeling, is het ontworpen om van Test-Driven Development een productieve en plezierige ervaring te maken.

RSpec biedt een krachtige DSL die handig is voor zowel unit- als integratietests. Hoewel ik RSpec heb gebruikt voor het schrijven van integratietests, gebruik ik het liever alleen in een testeenheid voor eenheden. Daarom zal ik ingaan op de manier waarop ik RSpec uitsluitend gebruik voor unit testing. Ik raad aan om The RSpec Book van David Chelimsky en anderen te lezen voor volledige en diepgaande RSpec-verslaggeving.


Komkommer

Ik heb ondervonden dat de voordelen van TDD / BDD veel groter zijn dan de nadelen.

Cucumber is een framework voor integratie en acceptatietests dat Ruby, Java, .NET, Flex en een groot aantal andere webtalen en -kaders ondersteunt. De ware kracht komt van zijn DSL; het is niet alleen beschikbaar in gewoon Engels, maar het is vertaald in meer dan veertig gesproken talen.

Met een door mensen leesbare acceptatietest kunt u de klant uitschrijven voor een functie voordat een enkele regel code wordt geschreven. Net als bij RSpec, zal ik alleen komkommer behandelen in de capaciteit waarin ik het gebruik. Voor de volledige rundown op komkommer, kijk op The Cucumber Book.


De opzet

Laten we eerst een nieuw project beginnen en Rails instrueren om de testunit over te slaan. Typ het volgende in een terminal:

 rails nieuw how_i_test -T

Binnen de Gemfile, toevoegen:

 source 'https: //rubygems.org'... groep: test do gem' capybara 'gem' cucumber-rails ', vereist: false gem' database cleaner 'gem' factory_girl_rails 'gem' shoulda 'eindgroep: ontwikkeling,: test do gem 'rspec-rails' einde

Ik gebruik meestal RSpec om ervoor te zorgen dat mijn modellen en hun methoden onder controle blijven.

Hier hebben we Komkommer en vrienden in de groep geplaatst test blok. Dit zorgt ervoor dat ze alleen goed worden geladen in de Rails-testomgeving. Merk op hoe we RSpec ook binnenin de ontwikkeling en test blokken, waardoor het beschikbaar is in beide omgevingen. Er zijn een paar andere edelstenen. welke ik hieronder kort zal beschrijven. Vergeet niet te rennen bundel installeren om ze te installeren.

  • Capybara: simuleert browserinteracties.
  • Database Cleaner: reinigt de database tussen testruns.
  • Fabrieksmeisje rails: vervanging van armatuur.
  • shoulda: helpermethoden en matchers voor RSpec.

We moeten de generators van deze edelstenen uitvoeren om ze op te zetten. U kunt dat doen met de volgende terminalopdrachten:

 rails g rspec: install create .rspec create spec create spec / spec_helper.rb rails g cucumber: install create config / cucumber.yml create script / cucumber chmod script / cucumber create features / step_definitions create features / support create features / support / env. rb bestaat uit lib / taken maken lib / tasks / cucumber.rake gsub config / database.yml gsub config / database.yml force config / database.yml

Op dit moment kunnen we beginnen met het schrijven van specificaties en cukes om onze applicatie te testen, maar we kunnen een paar dingen opzetten om het testen eenvoudiger te maken. Laten we beginnen in de application.rb het dossier.

 module HowITest class Application < Rails::Application config.generators do |g| g.view_specs false g.helper_specs false g.test_framework :rspec, :fixture => true g.fixture_replacement: factory_girl,: dir => 'spec / factories' end ... end end

Binnen de klasse Application overschrijven we enkele standaard generators van Rails. Voor de eerste twee, slaan we de specificaties van de weergaven en helpers over.

Deze tests zijn niet nodig, omdat we alleen RSpec gebruiken voor unit tests.

De derde regel informeert Rails dat we van plan zijn om RSpec te gebruiken als ons testraam van keuze, en het moet ook fixtures genereren bij het genereren van modellen. De laatste regel zorgt ervoor dat we factory_girl gebruiken voor onze armaturen, waarvan gemaakt in de spec / fabrieken directory.


Onze eerste functie

Om de zaken eenvoudig te houden, gaan we een eenvoudige functie schrijven om in te loggen bij onze applicatie. Kortheidshalve zal ik de daadwerkelijke implementatie overslaan en me aan de testsuite houden. Hier is de inhoud van features / signing_in.feature:

 Functie: aanmelden Om de toepassing te gebruiken Als geregistreerde gebruiker wil ik me aanmelden via een formulier Scenario: aanmelden via het formulier Gegeven is er een geregistreerde gebruiker met e-mail "[email protected]" En ik sta op het bord in pagina Wanneer ik de juiste inloggegevens invoert En ik druk op de knop Aanmelden. Dan moet het flashbericht "Signing in succesvol" zijn.

Wanneer we dit in de terminal uitvoeren met komkommerkenmerken / signing_in.feature, we zien veel output eindigend met onze ongedefinieerde stappen:

 Gegeven / ^ er is een geregistreerde gebruiker met e-mail "(. *?)" $ / Do | arg1 | in afwachting van # uitdrukking van de regexp hierboven met de code die u wenst te hebben Gegeven / ^ Ik ben op de aanmeldpagina $ / doe in behandeling # toon de regexp hierboven met de code die u wenst te beëindigen Wanneer / ^ Ik voer correcte inloggegevens in $ / in behandeling # toon de regexp hierboven met de code die je wenst te beëindigen Wanneer / ^ Ik druk op de inlogknop $ / doe in afwachting # druk de regexp hierboven uit met de code die je wenst te beëindigen Dan / ^ zou het flashbericht moeten zijn " (. *?) "$ / do | arg1 | In afwachting van # toon de regexp hierboven met de code die je wenst te beëindigen

De volgende stap is om te definiëren wat we verwachten dat elk van deze stappen zal doen. We drukken dit uit features / stepdefinitions / signin_steps.rb, gebruik makend van duidelijke Ruby met Capybara en CSS selectors.

 Gegeven / ^ er is een geregistreerde gebruiker met e-mail "(. *?)" $ / Do | email | @user = FactoryGirl.create (: user, email: email) end Given / ^ Ik ben op de aanmeldpagina $ / do visit sign_in_path end When / ^ Ik vul juiste inloggegevens $ / do fillin "Email" in, met: @user .email fillin "Wachtwoord", met: @ user.password end When / ^ Ik druk op de login knop $ / do click_button "Log in" end Dan / ^ zou het flashbericht "(. *?)" $ / do moeten zijn | tekst | binnen (".flits") moet page.should have_content tekst einde hebben

Binnen elk van de Gegeven, Wanneer, en Dan blokken gebruiken we de Capybara DSL om te definiëren wat we van elk blok verwachten (behalve in de eerste). In het eerste gegeven blok vertellen we factory_girl om een ​​gebruiker te maken die is opgeslagen in de gebruiker instantievariabele voor later gebruik. Als je rent komkommerkenmerken / signing_in.feature nogmaals, je zou iets moeten zien dat lijkt op het volgende:

 Scenario: aanmelden via het formulier # features / signing_in.feature: 6 Gegeven is er een geregistreerde gebruiker met e-mail "[email protected]" # features / step_definitions / signing \ _in \ _steps.rb: 1 Factory not registered: user ( ArgumentError) ./features/step_definitions/signing\_in\_steps.rb:2:in '/ ^ er is een geregistreerde gebruiker met e-mail "(. *?)" $ /' Features / signing_in.feature: 7: in 'Given er is een geregistreerde gebruiker met e-mail "[email protected]" '

We kunnen aan de hand van de foutmelding zien dat ons voorbeeld faalt op regel 1 met een ArgumentError van de gebruikersfabriek die niet is geregistreerd. We zouden deze fabriek zelf kunnen maken, maar met een deel van de magie die we eerder hebben ingesteld, zal Rails dat voor ons doen. Wanneer we ons gebruikersmodel genereren, krijgen we de gebruikersfabriek gratis.

 rails g model user email: string wachtwoord: string invoke active_record create db / migrate / 20121218044026 \ _create \ _users.rb create app / models / user.rb invoke rspec create spec / models / user_spec.rb invoke factory_girl create spec / fabrieken / gebruikers .rb

Zoals je ziet, roept de modelgenerator factory_girl aan en maakt het volgende bestand aan:

ruby spec / fabrieken / users.rb FactoryGirl.define doen fabriek: gebruiker e-mail "MyString" wachtwoord "MyString" end end

Ik ga hier niet uitgebreid ingaan op factory_girl, maar je kunt meer lezen in hun beknopte handleiding. Vergeet niet te rennen rake db: migreren en hark db: test: voorbereiden om het nieuwe schema te laden. Dit zou de eerste stap moeten zijn van onze functie om te slagen, en u op weg helpen om komkommer te gebruiken voor uw integratietests. Bij elke passage van je functies, zal Komkommer je naar de stukjes leiden die het ziet missen om het te laten passeren.


Model Testen met RSpec en Shoulda

Ik gebruik meestal RSpec om ervoor te zorgen dat mijn modellen en hun methoden onder controle blijven. Ik gebruik het ook vaak voor een aantal testen op hoog niveau, maar dat gaat meer in detail dan deze gids mogelijk maakt. We gebruiken hetzelfde gebruikersmodel dat we eerder hebben ingesteld met onze inlogfunctie. Terugkijkend op de output van het runnen van de modelgenerator, kunnen we zien dat we ook hebben user_spec.rb gratis. Als we vluchten rspec spec / models / user_spec.rb we zouden de volgende uitvoer moeten zien.

 In behandeling: Gebruiker kan enkele voorbeelden toevoegen aan (of verwijderen) /Users/janders/workspace/how\_i\_test/spec/models/user_spec.rb

En als we dat bestand openen, zien we:

 vereisen 'spechelper' beschrijven Gebruiker doen in behandeling "voeg enkele voorbeelden toe aan (of verwijder) # FILE" einde

De lopende regel geeft ons de uitvoer die we in de terminal hebben gezien. We maken gebruik van de ActiveRecord- en ActiveModel-matchers van Shoulda om ervoor te zorgen dat ons gebruikersmodel overeenkomt met onze bedrijfslogica.

 vereisen 'spechelper' beschrijven User do context "#fields" do it should respondto (: email) it should respondto (: password) it should replyto (: firstname) it should replyto (: lastname) end context "#validations" do it should validate_presence_of (: email) it should validate_presence_of (: password) it should validate_uniqueness_of (: email) einde context "#associations" do it should have_many (: tasks) end beschrijf "#methods" do let! (: user) FactoryGirl.create (: user) it "name moet de gebruikersnaam teruggeven" do user.name.should eql "Testy McTesterson" end end end

We hebben een paar contextblokken in onze eerste geplaatst beschrijven blokkeren om dingen zoals velden, validaties en associaties te testen. Hoewel er geen functionele verschillen zijn tussen een beschrijven en een context blok, er is een contextuele. We gebruiken beschrijven blokken om de status van wat we testen in te stellen, en context blokken om die tests te groeperen. Dit maakt onze tests op de lange termijn leesbaarder en onderhoudbaarder.

De eerste beschrijven stelt ons in staat om te testen tegen de Gebruiker model in een ongewijzigde staat.

We gebruiken deze ongewijzigde status om te testen tegen de database met de Shoulda-matchers die elk op type groeperen. De volgende beschrijven blok stelt een gebruiker in van onze eerder gemaakte gebruiker fabriek. De gebruiker instellen met de laat methode binnen dit blok stelt ons in staat om een ​​exemplaar van ons gebruikersmodel te testen aan de hand van bekende kenmerken.

Nu, wanneer we rennen rspec spec / models / user_spec.rb, we zien dat al onze nieuwe tests mislukken.

 Mislukt: 1) Gebruikernaam voor de methode moet de gebruikersnaam retourneren Fout / Fout: user.name.should eql "Testy McTesterson" NoMethodError: undefined method name 'for # # ./spec/models/user_spec.rb:26:inblok (3 niveaus) in '2) Validaties gebruiker # Failure / Error: it should validate_uniqueness_of (: email) Verwachte fouten om op te nemen "is al genomen" wanneer e-mail is ingesteld op "willekeurig"string ", kreeg geen fouten # ./spec/models/userspec.rb: 15: in 'block (3 niveaus) in '3) Validaties gebruiker # Failure / Error: it should validate_presence_of (: password) Verwachte fouten om op te nemen "can not be blank" wanneer wachtwoord is ingesteld op nul, geen fouten heeft # ./spec/models/user_spec.rb : 14: in 'block (3 niveaus) in '4) Validaties gebruiker # Failure / Error: it should validate_presence_of (: email) Verwachte fouten om op te nemen "can not be blank" wanneer e-mail is ingesteld op nul, geen fouten heeft # ./spec/models/user_spec.rb : 13: in 'block (3 niveaus) in '5) Gebruikers # verenigingen Fout / Fout: het zou moeten hebbenmany (: tasks) Verwachte gebruiker heeft een hasveel associatie genaamd taken (geen associatie genaamd taken) # ./spec/models/user_spec.rb:19:in 'block (3 niveaus) in '6) User # fields Failure / Error: it zou moeten reagerentot en met (: laatstenaam) verwacht # om te reageren op: lastnaam # ./spec/models/userspec.rb: 9: in 'block (3 niveaus) in '7) User # fields Failure / Error: it zou moeten reagerentot en met (: eerstenaam) verwacht # om te reageren op: eerstnaam # ./spec/models/userspec.rb: 8: in blok (3 niveaus) in '

Met elk van deze tests faalt, hebben we het raamwerk dat we nodig hebben om migraties, methoden, associaties en validaties toe te voegen aan onze modellen. Naarmate onze applicatie evolueert, modellen uitbreiden en ons schema verandert, biedt dit niveau van testen ons bescherming voor het introduceren van brekende veranderingen.


Conclusie

Hoewel we niet al te veel onderwerpen uitvoerig hadden behandeld, zou je nu een basiskennis moeten hebben van integratie en unit testing met komkommer en RSpec. TDD / BDD is een van de dingen die ontwikkelaars lijken te doen of niet doen, maar ik heb gemerkt dat de voordelen van TDD / BDD veel groter zijn dan de nadelen bij meer dan één gelegenheid.