Lucrarea mea recentă a fost făcută pe un proiect bazat pe nor, Ruby pentru BBC News, viitoarele alegeri din 2014. Aceasta necesită I / O rapidă, scalabilitate și trebuie să fie bine testată. Cerința "bine testată" este ceea ce vreau să mă concentrez în acest tutorial.
Acest proiect utilizează câteva servicii Amazon diferite, cum ar fi:
Trebuie să fim capabili să scriem teste care sunt rapide și să ne dea feedback instantaneu asupra problemelor legate de codul nostru.
Deși nu vom folosi serviciile Amazon în acest tutorial, le menționez pentru că pentru a avea teste care sunt rapide, ne cere să falsăm aceste obiecte externe (de exemplu, nu ar trebui să avem nevoie de o conexiune de rețea pentru a ne rula teste, deoarece această dependență poate duce la teste de funcționare lentă).
Alaturi de tehnicianul Robert Kenny (care este foarte bine pregatit in dezvoltarea aplicatiilor Ruby TDD (based on test-based), am folosit diferite instrumente care au facut acest proces si programul nostru de lucru mult mai usor.
Vreau să vă împărtășesc câteva informații despre aceste instrumente.
Instrumentele pe care le voi acoperi sunt:
Voi face o presupunere că sunteți familiarizat cu codul Ruby și ecosistemul Ruby. De exemplu, nu ar trebui să vă explic cum sunt "pietrele prețioase" sau cum funcționează anumite sintaxe / concepte Ruby.
Dacă nu sunteți sigur, atunci înainte de a vă deplasa, vă recomandăm să citiți una dintre celelalte postări ale mele pe Ruby pentru a vă ridica la viteză.
S-ar putea să nu fiți familiarizați cu Garda, dar în esență este un instrument de linie de comandă care utilizează Ruby pentru a gestiona diferite evenimente.
De exemplu, Garda vă poate notifica ori de câte ori anumite fișiere au fost editate și puteți efectua anumite acțiuni în funcție de tipul de fișier sau eveniment care a fost concediat.
Acest lucru este cunoscut ca un "alergător de sarcini", probabil că ați auzit fraza înainte, deoarece în prezent sunt foarte folosiți în lumea frontului / clientului (Grunt și Gulp sunt două exemple populare).
Motivul pentru care vom folosi Garda este pentru că ajută la întărirea buclă de feedback (atunci când faci TDD). Aceasta ne permite să editați fișierele noastre de testare, să examinăm un test de eroare, să actualizăm și să salvăm codul nostru și să vedem imediat dacă trece sau nu (în funcție de ceea ce am scris).
Ați putea folosi ceva asemănător cu Grunt sau Gulp, dar preferăm să folosiți acele tipuri de alergători pentru a manipula lucruri de la front-end / client. Pentru codul back-end / server-side, folosim Rake și Guard.
RSpec, dacă nu știați deja, este un instrument de testare pentru limbajul de programare Ruby.
Puteți executa testele (utilizând RSpec) prin linia de comandă și voi demonstra cum puteți face acest proces mai ușor prin utilizarea programului de construire a lui Ruby, Rake.
În sfârșit, vom folosi o altă bijuterie Ruby numită Pry, care este un instrument extrem de puternic de depanare a erorilor Ruby, care se injectează în aplicația dvs., în timp ce rulează, pentru a vă permite să inspectați codul și să aflați de ce ceva nu funcționează.
Deși nu este necesar pentru a demonstra utilizarea RSpec și Guard, merită menționat faptul că susțin pe deplin utilizarea TDD ca mijloc de a asigura că fiecare linie de cod pe care o scrieți are un scop și a fost proiectată într-o manieră testabilă și fiabilă.
Voi detalia cum am face TDD cu o aplicație simplă, deci cel puțin veți obține o simțire a modului în care funcționează procesul.
Am creat un exemplu de bază pentru GitHub, pentru a vă salva de la faptul că trebuie să tastați totul singur. Nu ezitați să descărcați codul.
Să începem acum și să examinăm acest proiect, pas cu pas.
Există trei fișiere primare necesare pentru ca aplicația noastră de exemplu să funcționeze, acestea sunt:
Gemfile
Guardfile
Rakefile
Vom trece în scurt timp conținutul fiecărui fișier, dar primul lucru pe care trebuie să-l facem este să introducem structura noastră de directoare.
Pentru proiectul nostru de exemplu, vom avea nevoie de două dosare create:
lib
(acest lucru va conține codul nostru de aplicare)spec
(aceasta va conține codul nostru de testare)Aceasta nu este o cerință pentru aplicația dvs., puteți modifica cu ușurință codul în cadrul celorlalte fișiere pentru a lucra cu structura potrivită.
Deschideți terminalul și executați următoarea comandă:
gem install bundler
Bundler este un instrument care facilitează instalarea altor pietre prețioase.
Odată ce ați executat comanda respectivă, creați cele trei fișiere de mai sus (Gemfile
, Guardfile
și Rakefile
).
Gemfile
este responsabil pentru definirea unei liste de dependențe pentru aplicația noastră.
Iată cum arată:
sursă "https://rubygems.org" gem 'rspec "grup: dezvoltare do gem' paza 'gem' guard-rspec 'gem' pry 'end
Odată ce acest fișier este salvat, executați comanda instalare pachet
.
Acest lucru va instala toate pietrele noastre pentru noi (inclusiv cele pietre precizate în cadrul dezvoltare
grup).
Scopul dezvoltare
(care este o caracteristică specifică a pachetului) este atunci când implementați aplicația dvs., puteți spune mediului dvs. de producție să instaleze numai pietrele care sunt necesare pentru ca aplicația să funcționeze corect.
Deci, de exemplu, toate pietre în interiorul dezvoltare
grup, nu sunt necesare pentru buna funcționare a aplicației. Acestea sunt folosite numai pentru a ne ajuta în timp ce dezvoltăm și testează codul nostru.
Pentru a instala pietre potrivite pe serverul de producție, ar trebui să executați ceva de genul:
pachet de instalare - fără dezvoltare
Rakefile
ne va permite să executăm testele RSpec din linia de comandă.
Iată cum arată:
cereți "rspec / core / rake_task" RSpec :: Core :: RakeTask.new nu | task.rspec_opts = ['--color', '--format', 'doc'] sfârșit
Notă: nu aveți nevoie de gardă pentru a putea executa testele RSpec. Folosim Garda pentru a face mai ușor TDD-ul.
Când instalați RSpec, vă oferă acces la o sarcină construită în Rake și asta este ceea ce folosim aici.
Creăm o nouă instanță de RakeTask
care implicit creează o sarcină numită spec
care va căuta un dosar numit spec
și va rula toate fișierele de testare din acel folder, utilizând opțiunile de configurare pe care le-am definit.
În acest caz, dorim ca outputul shell-ului nostru să aibă culori și dorim să formăm ieșirea la medic
stil (puteți schimba formatul care urmează să fie cuibărit
ca exemplu).
Puteți configura sarcina Rake să funcționeze în orice fel doriți și să căutați în directoare diferite, dacă asta aveți. Dar setările implicite funcționează excelent pentru aplicația noastră și așa vom folosi.
Acum, dacă vreau să rulez testele în depozitul GitHub al exemplului, atunci trebuie să-mi deschid terminalul și să execut comanda:
rake spec
Acest lucru ne oferă următoarea ieșire:
rake spec / bin / ruby -S rspec ./spec/example_spec.rb --color --format doc RSpecGreeter RSpecGreeter # greet () Terminat în 0.0006 secunde 1 exemplu, 0 eșecuri
După cum puteți vedea, există eșecuri zero. Asta pentru că, deși nu avem codul aplicației scrise, nu avem încă nici un cod de test scris.
Conținutul acestui fișier spune Garda ce să facă atunci când rulați pază
comanda:
pază "rspec" nu # watch / lib / fișiere ceas (% r ^ lib /(.+). "spec / # m] [1] _ spec.rb" sfârșitul # ceas / spec / fișiere ceas (% r ^ spec /(.+). "spec / # m [1]. rb" sfârșitul final
Veți observa în interiorul nostru Gemfile
am specificat bijuteria: guard-rspec
. Avem nevoie de acea bijuterie pentru a permite Gardienilor să înțeleagă cum să se ocupe de modificările aduse fișierelor legate de RSpec.
Dacă ne uităm din nou la conținut, putem vedea că dacă am fugit paza rspec
atunci Guard urma să urmărească fișierele specificate și să execute comenzile specificate odată ce s-au produs modificările acestor fișiere.
Notă: pentru că avem doar o sarcină de pază, rspec
, atunci aceasta se execută în mod implicit dacă am executat comanda pază
.
Poți să vezi că Garda ne oferă ceas
funcția pe care noi o transmitem printr-o Expresie regulată pentru a ne permite să definim ce fișiere suntem interesați să urmărim Garda.
În acest caz, îi spunem Gardei să vadă toate fișierele din cadrul nostru lib
și spec
foldere și în cazul în care orice modificări apar la oricare dintre aceste fișiere, apoi pentru a executa fișierele de testare în cadrul nostru spec
dosar pentru a vă asigura că nu s-au făcut modificări pe care le-am încălcat testele noastre (și, ulterior, nu ne-am rupt codul).
Dacă aveți toate fișierele descărcate de la GitHub repo, atunci puteți încerca comanda pentru tine.
Alerga pază
și apoi salvați unul din fișiere pentru a vedea că rulează testele.
Înainte de a începe să examinăm un cod de testare și de aplicare, permiteți-mi să explic ce va face aplicația noastră. Aplicația noastră este o singură clasă care va returna un mesaj de salut tuturor celor care execută codul.
Cerințele noastre sunt simplificate intenționat, deoarece vor face mai ușor înțelegerea procesului pe care urmează să-l realizăm.
Să luăm acum o privire la un exemplu de specificație (de exemplu, fișierul nostru de testare) care va descrie cerințele noastre. După aceasta, vom începe să trecem prin codul definit în caietul de sarcini și vom vedea cum putem folosi TDD pentru a ne ajuta în scrierea cererii noastre.
Vom crea un fișier intitulat example_spec.rb
. Scopul acestui fișier este de a deveni fișierul cu specificațiile noastre (cu alte cuvinte, acesta va fi codul nostru de testare și va reprezenta funcționalitatea așteptată).
Motivul pentru care scriem codul nostru de testare înainte de a scrie codul nostru actual de aplicație este că, în cele din urmă, înseamnă că orice cod de aplicație pe care îl producem va exista pentru că a fost folosit de fapt.
Acesta este un punct important pe care îl fac și permiteți-mi să iau o clipă pentru ao clarifica mai detaliat.
În mod tipic, dacă mai întâi scrieți codul aplicației (deci nu faceți TDD), atunci veți găsi că scrieți un cod care, la un moment dat în viitor, este depășit și ar putea fi depășit. Prin procesul de refactorizare sau schimbarea cerințelor, puteți constata că anumite funcții nu vor fi niciodată chemate.
Acesta este motivul pentru care TDD este considerată o practică mai bună și o metodă de dezvoltare preferată de utilizat, deoarece fiecare linie de cod pe care o produceți va fi produsă dintr-un motiv: pentru a obține o specificație defectuoasă (cerința dvs. reală de afaceri) să treacă. Este un lucru foarte puternic pe care să-l aveți în minte.
Iată codul nostru de testare:
cer "spec_helper" descrie 'RSpecGreeter' fa -l 'RSpecGreeter # greet ()' do greeter = RSpecGreeter.new # dat salut = greeter.greet #
Este posibil să observați comentariile codului la sfârșitul fiecărui rând:
Dat
Cand
Atunci
Acestea sunt o formă de terminologie BDD (Development-Driven Development). Le-am inclus pentru cititorii care sunt mai familiarizați cu BDD (Behavior-Driven Development) și care au fost interesați de modul în care pot echivala aceste afirmații cu TDD.
Primul lucru pe care îl facem în acest fișier este încărcarea spec_helper.rb
(care se găsește în același director ca fișierul nostru de spec.). Vom reveni și vom privi conținutul fișierului într-o clipă.
Apoi avem două blocuri de coduri specifice pentru RSpec:
descrie "x"
ce faci
Primul descrie
bloc ar trebui să descrie în mod adecvat clasa / modulul pe care lucrăm și să oferim teste pentru. Ai putea avea foarte multe descrie
blochează într-un singur fișier cu specificații.
Există multe teorii diferite cu privire la modul de utilizare descrie
și aceasta
blocuri de descriere. Eu personal prefer simplitatea si astfel voi folosi identificatorii pentru clasa / module / metode pe care le vom testa. Dar găsiți adesea unii oameni care preferă să folosească propoziții complete pentru descrierile lor. Nici nu este bine sau nu, doar preferința personală.
aceasta
bloc este diferit și ar trebui să fie întotdeauna plasat în interior descrie
bloc. Ar trebui să explice Cum dorim ca aplicația noastră să funcționeze.
Din nou, ați putea folosi o propoziție normală pentru a descrie cerințele, dar am constatat că uneori acest lucru poate determina descrierile să fie prea explicite, când ar trebui să fie într-adevăr mai mult implicit. Fiind mai puțin explicită reduce șansele de schimbare a funcționalității dvs., determinând descrierea dvs. să devină depășită (trebuie să actualizați descrierea de fiecare dată când apar modificări minore ale funcționalității, este mai mult o sarcină decât un ajutor). Folosind identificatorul metodei pe care o testăm (de exemplu, numele metodei pe care o executăm) putem evita acea problemă.
Conținutul mesajului aceasta
bloc este codul pe care îl vom testa.
În exemplul de mai sus, creăm o nouă instanță a clasei RSpecGreeter
(care nu există încă). Trimitem mesajul întâmpina
(care, de asemenea, nu există încă) pentru obiectul instanțiat creat (Notă: aceste două linii sunt codul Ruby standard în acest moment).
În cele din urmă, spunem cadrului de testare că ne așteptăm la rezultatul chemării întâmpina
metoda de a fi textul "Bună ziua RSpec!
", utilizând sintaxa RSpec: echiv (ceva)
.
Observați cum permite sintaxa să fie ușor de citit (chiar de către o persoană non-tehnică). Acestea sunt cunoscute sub numele de afirmații.
Există o mulțime de afirmații RSpec diferite și nu vom intra în detalii, dar nu ezitați să revizuiți documentația pentru a vedea toate caracteristicile pe care RSpec le oferă.
Există o anumită cantitate de boilerplate necesară testărilor noastre. În acest proiect, avem doar un fișier de specificații, dar într-un proiect real este posibil să aveți zeci (în funcție de dimensiunea aplicației).
Pentru a ne ajuta să reducem codul de boilerplate, îl vom plasa în interiorul unui fișier special de ajutor pe care îl vom încărca din fișierele cu specificații. Acest fișier va fi intitulat spec_helper.rb
.
Acest fișier va face câteva lucruri:
desface
gem (ne ajută să depanem codul nostru, dacă este necesar).Iată codul:
$ << File.join(File.dirname(FILE), '… ', 'lib') require 'pry' require 'example'
Notă: prima linie poate părea un pic criptică, așa că permiteți-mi să explic cum funcționează. Aici spunem că vrem să adăugăm / Lib /
dosarul lui Ruby $ LOAD_PATH
variabilă sistem. Ori de câte ori Ruby evaluează cere "some_file"
are o listă de directoare pe care va încerca să le găsească. În acest caz, ne asigurăm că dacă avem codul cereți "exemplu"
că Ruby va fi capabil să-l localizeze pentru că ne va verifica / Lib /
director și acolo, va găsi fișierul specificat. Acesta este un truc inteligent pe care îl veți vedea folosit într-o mulțime de pietre Ruby, dar poate fi destul de confuz dacă nu ați mai văzut-o înainte.
Codul nostru de aplicare va fi în interiorul unui fișier intitulat example.rb
.
Înainte de a începe să scrieți orice cod de aplicație, amintiți-vă că facem acest proiect TDD. Așa că vom lăsa ca testele din fișierul cu caietul de sarcini să ne ghideze pe ce să facem mai întâi.
Să începem presupunând că o folosiți pază
pentru a rula testele dvs. (de fiecare dată când facem o schimbare example.rb
, Garda va observa schimbarea și va continua să ruleze example_spec.rb
pentru a vă asigura că testele noastre trec).
Pentru ca noi să facem TDD în mod corespunzător, nostru example.rb
fișierul va fi gol și dacă deschidem fișierul și îl salvăm în starea sa actuală, Guard va rula și vom descoperi (în mod surprinzător) că testul nostru va eșua:
Eșecurile: 1) RSpecGreeter RSpecGreeter # greet () Eroare / eroare: greeter = RSpecGreeter.new # Datând numeleError: constanta necondiționată RSpecGreeter # ./spec/example_spec.rb:5:inbloc (2 nivele) în 'Finalizat în 0.00059 secunde 1 exemplu, 1 eșec Exemple greșite: rspec ./spec/example_spec.rb:4 # RSpecGreeter RSpecGreeter # greet ()
Acum, înainte de a merge mai departe, permiteți-mi să clarific din nou că TDD se bazează pe premisa că fiecare linie de cod are un motiv să existe, deci nu face începeți curse înainte și scrieți mai mult cod decât aveți nevoie. Scrieți doar cantitatea minimă de cod necesară pentru ca testul să treacă. Chiar dacă codul este urât sau nu îndeplinește funcționalitatea completă.
Punctul lui TDD este acela de a fi strâns bucla de feedback, cunoscută și ca "roșu, verde, refactor"). Ce înseamnă acest lucru în practică este:
Veți vedea într-o clipă că, deoarece cerințele noastre sunt atât de simple, nu este nevoie ca noi să refacem. Dar într-un proiect real cu cerințe mult mai complexe, probabil că va trebui să faceți al treilea pas și să refaceți codul pe care l-ați introdus, pentru ca testul să treacă.
Revenind la testul nostru de eșec, așa cum puteți vedea în eroare, nu există RSpecGreeter
clasă definită. Să rezolvăm acest lucru și să adăugăm următorul cod și să salvăm fișierul astfel încât testele să fie executate:
clasa RSpecGreeter # cod va merge în cele din urmă aici sfârșitul
Aceasta va duce la următoarea eroare:
Erori: 1) RSpecGreeter RSpecGreeter # greet () Eroare / Eroare: greeter = greeter.greet # Când NoMethodError: undefined methodgreet 'pentru # # .spec/example_spec.rb:6:in block (2 nivele) 0.00036 secunde 1 exemplu, 1 eșec
Acum vedem că această eroare ne spune metoda întâmpina
nu există, deci să o adăugăm și apoi să salvăm din nou fișierul nostru pentru a rula testele noastre:
clasa RSpecGreeter def salut # cod va veni în cele din urmă aici sfârșitul sfârșitul
OK, suntem aproape acolo. Eroarea pe care o avem acum este:
Erori: 1) RSpecGreeter RSpecGreeter # greet () Eșec / greșeală: greeter = greeting.should eq ('Hello RSpec!') # Apoi așteptați: "Hello RSpec! a primit: nil (comparat folosind ==) # ./spec/example_spec.rb:7:in 'bloc (2 niveluri) în' Finalizat în 0.00067 secunde 1 exemplu, 1 eșec
RSpec ne spune că se aștepta să vadă Bună ziua RSpec!
dar în schimb a ajuns zero
(pentru că am definit întâmpina
dar nu a definit nimic în interiorul metodei și astfel se întoarce zero
).
Vom adăuga restul de cod care ne-ar face testul să treacă:
clasa RSpecGreeter def salut "Hello RSpec!" sfârșitul final
Acolo o avem, un test de trecere:
Terminat în 0.00061 secunde 1 exemplu, 0 eșecuri
Am terminat aici. Testul nostru este scris și codul trece.
Până acum, am aplicat un proces de dezvoltare bazat pe teste pentru a construi aplicația noastră, alături de utilizarea cadrului popular de testare RSpec.
O vom lăsa aici deocamdată. Întoarce-te și adaugă-ne pentru partea a doua, unde vom analiza mai multe caracteristici RSpec specifice, precum și folosind gemul Pry pentru a vă ajuta să depanați și să scrieți codul.