Nylig har jeg gjort en betydelig mengde CoffeeScript-arbeid. Et problem jeg kjørte tidlig på, var testing: Jeg ville ikke manuelt konvertere CoffeeScript til JavaScript før jeg kunne teste den. I stedet ønsket jeg å teste fra CoffeeScript direkte. Hvordan slutte jeg med å gjøre det? Les videre for å finne ut!
Publisert opplæringNoen få uker besøker vi noen av leserens favorittinnlegg fra hele historien til nettstedet. Denne opplæringen ble først publisert i november 2012.
Du må ha Node.js og Node Package Manager installert.
Før vi fortsetter, vil jeg påpeke at du må ha en anstendig kjennskap til CoffeeScript for denne opplæringen; Jeg vil ikke forklare biter og stykker her. Hvis du er interessert i CoffeeScript, bør du sjekke ut CoffeeScript-knappene som er tilgjengelige her på Nettuts +, eller CoffeeScript-dokumentasjonen.
I tillegg må du ha Node.js og Node Package Manager (npm) installert for denne opplæringen. Hvis du ikke har dem installert, ingen bekymringer: gå over til nodejs.org og last ned installasjonsprogrammet for plattformen din; da, vel, installer det!
Vi skal bygge begynnelsen av en todo-liste søknad (cliché, jeg vet). Disse blir CoffeeScript-klasser. Deretter skal vi skrive noen tester med Mocha og Chai for å teste den funksjonaliteten.
Hvorfor både Mocha og Chai? Vel, Mocha er en testing rammeverk, men det inkluderer ikke selve påstanden komponent. Det høres kanskje rart ut: tross alt er det ikke mye mer til et testbibliotek, er det? Vel, det er, i Mokka saken. Funksjonene som brakte meg til biblioteket er to ganger: muligheten til å kjøre tester fra kommandolinjen (i stedet for å ha en HTML-side for å kjøre dem i nettleseren), og muligheten til å kjøre test i CoffeeScripts, uten å måtte konvertere som kodes til JavaScript (som minst manuelt: Mokka gjør det bak kulissene). Det er også andre funksjoner som jeg ikke snakker om her, inkludert:
Og videre og videre. Se mer på Mokka-hjemmesiden. For å installere Mocha bare kjøre npm installere -g mocha
, og du er satt.
Som for Chai: det er et flott påstandsbibliotek som tilbyr grensesnitt for å gjøre både BDD og TDD; Du kan bruke det både i nettleseren eller på kommandolinjen via node, slik bruker vi det i dag. Installer det for Node, via npm installere -g chai
.
Nå som vi har installert bibliotekene våre, la oss begynne å skrive noen kode.
La oss begynne med å sette opp et mini-prosjekt. Lag en prosjektmappe. Deretter oppretter du to mapper i den ene: src
, og test
. Vår CoffeeScript kode vil gå inn i src
mappe, og våre tester vil gå inn, du gjettet det, tester
mappe. Mokka ser etter en test
mappe som standard, så ved å gjøre dette, lagrer vi oss selv litt senere.
Mokka ser etter en
test
mappen som standard.
Vi skal lage to CoffeeScript-klasser: Oppgave
, som vil være en todo gjenstand, og Oppgaveliste
, som vil være en liste over todo elementer (ja, det er mer enn en matrise). Vi legger dem begge i src / task.coffee
fil. Deretter vil testene for dette være i test / taskTest.coffee
. Selvfølgelig kan vi dele dem inn i egne filer, men vi skal bare ikke gjøre det i dag.
Vi må begynne med å importere Chai-biblioteket og aktivere BDD-syntaksen. Dette er hvordan:
chai = krever 'chai' chai.should ()
Ved å ringe chai.should
metode, vi legger faktisk til en bør
eiendom til Object.prototype
. Dette tillater oss å skrive tester som leser slik:
task.name.should.equal "litt streng"
Hvis du foretrekker TDD-syntaksen, kan du gjøre dette:
Forvent = Chai.expect
... som lar deg skrive tester som dette:
forventer (oppgave.navn) .til ekvivalent "litt streng"
Vi må faktisk bruke begge disse, som du vil se; Vi bruker imidlertid BDD-syntaksen så mye som mulig.
Nå må vi importere vår Oppgave
og Oppgaveliste
klasser:
TaskList, List = krever '... / src / task'
Hvis du ikke er kjent med denne syntaksen, er det CoffeeScripts ødelagte oppgave på jobben, så vel som noen av dens objekt, bokstavelig sukker. I utgangspunktet vår krever
anrop returnerer et objekt med to egenskaper, som er våre klasser. Denne linjen trekker dem ut av objektet og gir oss to variabler som heter Oppgave
og Oppgaveliste
, som hver peker til den respektive klassen.
Flott! Nå, hva med en test? Skjønnheten i Mocha-syntaksen er at dens blokker (beskrive
og den
) er identiske med jasmines (begge er svært lik RSpec). Her er vår første test:
Beskriv 'Oppgaveeksempel', -> oppgave1 = oppgave2 = null det 'skal ha et navn', -> oppgave1 = ny oppgave 'feed katten' oppgave1.navn.should.equal 'feed katten'
Vi starter med en beskrive
ring: alle disse testene er for en test-forekomst. Ved innstilling test1 = test2 = null
utenfor våre individuelle tester, kan vi bruke disse verdiene for flere tester.
Så, i vår første test, oppretter vi bare en oppgave og sjekker for å se at navnet på eiendommen har den riktige verdien. Før du skriver koden for dette, la oss legge til to flere tester:
det skal være i utgangspunktet ufullstendig », -> oppgave1.status.should.equal 'ufullstendig' det 'skal kunne fullføres', -> task1.complete (). should.be.true task1.status.should.equal 'fullstendig'
Ok, la oss kjøre disse testene for å sikre at de svikter. For å gjøre dette, la oss åpne en ledetekst og cd
til prosjektmappen din. Kjør deretter denne kommandoen:
mocha - compilers kaffe: kaffe-script
Mokka sjekker ikke for CoffeeScript som standard, så vi må bruke --kompilatorer
flagg for å fortelle Mocha hvilken kompilator å bruke hvis den finner en fil med kaffe
filutvidelse. Du bør få feil som ser slik ut:
Hvis, i stedet for å se det, får du feilen Kan ikke finne modulen '... / src / task'
, det er fordi din src / task.coffee
filen eksisterer ikke ennå. Lag nevnte fil, og du burde få den nevnte feilen.
Vel, nå at vi har sviktende tester, er det på tide å skrive koden, riktig? Åpne det src / task.coffee
fil og la oss få sprekker.
klasse Oppgavekonstruktør: (@name) ->
Bare dette er nok til å få vår første testkjøring. Hvis du ikke er kjent med parameterparametrene, setter det bare hva verdien er sendt til ny oppgave
til @Navn
(eller this.name
) eiendom. La oss imidlertid legge til en annen linje til den konstruktøren:
@status = 'ufullstendig'
Det er bra. Nå, gå tilbake til terminalen og kjøre testene våre igjen. Du finner det - vent et sekund, ingenting er forandret! Hvorfor går ikke våre første to tester forbi?
Et enkelt problem, faktisk. Fordi CoffeeScript-kompilatoren bryter inn koden i hver fil i en IIFE (eller en egenaktiverende anonym funksjon), må vi "eksportere" alt vi vil være tilgjengelig fra andre filer. I nettleseren vil du gjøre noe sånt window.Whatever = hva som helst
. For Node, kan du enten bruke global
eller eksport
. Vi skal bruke eksport
, siden 1) det anses best praksis, og 2) det er det vi forberedte på når vi satte opp våre tester (husk vår krever
anrop?). Derfor, på slutten av vår task.coffee
fil, legg til dette:
root = eksport? vindu root.Task = Oppgave
Med det på plass, bør du oppdage at to av våre tre tester nå går forbi:
For å få den siste testen til å passere, må vi legge til en fullstendig
metode. Prøv dette:
fullfør: -> @status = 'fullfør' true
Nå passerer alle tester:
Nå er det en god tid å nevne at Mokka har en rekke forskjellige rapporter: Dette er bare forskjellige måter å levere testresultatene på. Du kan kjøre mocha - reporters
å se alternativene dine:
Som standard bruker Mocha prikk reporteren. Men jeg foretrekker spec reporteren, så jeg takker -R spec
på slutten av kommandoen (-R
er reporter-innstillingen flagg).
La oss legge til en funksjon i vår Oppgave
Klasse: Vi lar oppgaver være avhengige av andre oppgaver. Hvis oppgaven "foreldre" ikke er fullført, kan ikke oppgaven "barn" gjøres. Vi beholder denne funksjonen enkel og tillater at oppgaver kun har én underoppgave. Vi vil heller ikke sjekke om rekursivitet, så mens det blir mulig å sette to oppgaver for å være foreldre og barn til hverandre, vil det gjøre begge oppgavene ufullstendige.
Prøver først!
det skal kunne være avhengig av en annen oppgave, -> task1 = new Task 'wash dishes' task2 = ny oppgave 'tørre retter' task2.dependsOn task1 task2.status.should.equal 'dependent' task2.parent.should .equal task1 task1.child.should.equal task2 den 'bør nekte fullføring det er avhengig av en ufullstendig oppgave', -> (-> task2.complete ()). should.throw "Avhengige oppgave" vaskefat "er ikke fullført ."
Oppgave
forekomster kommer til å ha a kommer an på
metode, hvilke oppgaver oppgaven som blir deres overordnet. Oppgaver som har en overordnet oppgave, skal ha status som "avhengig". Også begge oppgaver får enten a forelder
eller barn
eiendom som peker på riktig oppgaveeksempel.
I den andre testen der, sier vi at en oppgave med en ufullstendig foreldreoppgave burde kaste en feil når det er fullstendig
Metoden kalles. Legg merke til hvordan testsyntaxen fungerer: vi må ringe bør
av en funksjon, og ikke resultatet av funksjonen: derfor vikler vi funksjonen i parentes. På denne måten kan testbiblioteket ringe selve funksjonen og se etter feilen.
Kjør disse testene, og du vil se at begge mislykkes. Kodingstid!
dependsOn: (@parent) -> @ parent.child = @ @status = 'avhengig'
Igjen, veldig enkelt: Vi har bare satt opp oppgaveparameteren til foreldreoppgaven, og gi den en barnegenskap som peker på dette
oppgavetilfelle. Deretter setter vi statusen til dette
Oppgave å være "avhengig".
Hvis du kjører dette nå, vil du se at en av våre tester går forbi, men den andre er ikke: det er fordi vår fullstendig
Metoden sjekker ikke for en ufullført foreldreoppgave. La oss endre det.
fullfør: -> hvis @parent? og @ parent.status er ikke "fullført" kaste "Avhengig oppgave" #@parent.name 'er ikke fullført. " @status = 'fullfør' true
Her er det ferdige fullstendig
Metode: Hvis det er en overordnet oppgave, og det er ikke fullført, kaster vi en feil. Ellers fullfører vi oppgaven. Nå skal alle tester passere.
Deretter skal vi bygge Oppgaveliste
klasse. Igjen begynner vi med en test:
beskriv 'TaskList', -> taskList = null det 'skal starte uten oppgaver', -> taskList = new TaskList taskList.tasks.length.should.equal 0 taskList.length.should.equal 0
Dette er gamle hue til deg nå: vi lager en Oppgaveliste
objekt og sjekke dens oppgaver
og lengde
egenskaper for å sikre at de begge er null. Som du kanskje gjetter, oppgaver
er en matrise som holder oppgavene, mens lengde
er bare en praktisk egenskap som vi vil oppdatere når du legger til eller fjerner oppgaver; det sparer oss bare fra å måtte skrive list.tasks.length
.
For å få denne testen til å passere, gjør vi denne konstruktøren:
klasse TaskList constructor: () -> @tasks = [] @length = 0
God start, og det får testen vår.
Vi vil være i stand til å legge til oppgaver i en oppgaveliste, ikke sant? Vi har en Legg til
metode som kan ta enten a Oppgave
eksempel eller en streng som den vil konvertere til a Oppgave
forekomst.
Våre tester:
det skal akseptere nye oppgaver som oppgaver, -> oppgave = ny oppgave 'kjøp melk' taskList.add oppgave taskList.tasks [0] .name.should.equal 'buy milk' taskList.length.should.equal 1 det ' bør godta nye oppgaver som streng ', -> taskList.add' ta ut søppel 'taskList.tasks [1] .name.should.equal' ta ut søppel 'taskList.length.should.equal 2
Først legger vi til en faktisk Oppgave
objekt, og kontroller taskList.tasks
array for å bekrefte at den er lagt til. Deretter legger vi til en streng, og sørger for at a Oppgave
objekt med riktig navn ble lagt til i oppgaver
array. I begge tilfeller kontrollerer vi lengden på oppgaveliste
også, for å sørge for at det blir eiendommen oppdatert.
Og funksjonen:
legg til: (oppgave) -> hvis type av oppgave er 'string' @ tasks.push ny Oppgaveoppgave ellers @ tasks.push oppgave @length = @ tasks.length
Ganske selvforklarende, tror jeg. Og nå passerer våre tester:
Selvfølgelig vil vi kanskje fjerne oppgaver fra listen vår, til høyre?
det 'burde fjerne oppgaver', -> i = taskList.length - 1 taskList.remove taskList.tasks [jeg] forventer (taskList.tasks [i]). to.not.be.ok
Først kaller vi fjerne
metode (ennå å bli skrevet, selvfølgelig), bestått den siste oppgaven for tiden i listen. Jo, vi kunne bare skrive inn indeksen 1
, men jeg har gjort det på denne måten fordi det gjør denne testen fleksibel: Hvis vi endret våre tidligere tester eller lagt til flere tester over dette, må det kanskje endres. Selvfølgelig må vi fjerne den siste fordi ellers vil oppgaven etter det ta sin plass og det vil være noe i den indeksen når vi forventer at det ikke skal være noe.
Og snakker om å forvente, merk at vi bruker forvente
funksjon og syntaks her i stedet for vår vanlige bør
. Dette er fordi taskList.tasks [i]
vil være udefinert
, som ikke arver fra Object.prototype
, og derfor kan vi ikke bruke bør
.
Å, ja, vi trenger fortsatt å skrive det fjerne
funksjon:
fjern: (oppgave) -> i = @ tasks.indexOf oppgave @tasks = @tasks [0 ... i] .concat @tasks [i + 1 ...] hvis jeg> -1 @length = @ tasks.length
Noen fancy array footwork kombinert med CoffeeScript's rekkevidde og array splicing shorthand lukker denne avtalen for oss. Vi splitter ganske enkelt av alle elementene før den som skal fjernes og alle elementene etter det; vi concat
de to arrays sammen. Selvfølgelig oppdaterer vi @lengde
tilsvarende. Kan du si "bestått test"?
La oss gjøre en ting til. Vi ønsker å skrive ut vår (relativt) flotte liste over gjeldende oppgaver. Dette vil bli vår mest komplekse (eller i det minste, vår lengste) testen ennå:
det skal skrive ut listen ', -> taskList = new TaskList task0 = new Task' buy milk 'task1 = ny Oppgave' gå til butikk 'task2 = ny Oppgave' en annen oppgave 'task3 = ny Oppgave' underoppgave 'oppgave4 = ny oppgave 'sub-oppgave' oppgaveliste.add oppgave0 oppgaveliste.add oppgave1 oppgaveliste.add oppgave2 oppgaveliste.add oppgave3 oppgaveliste.add oppgave4 oppgave0differentiell oppgave1 oppgave4oppgaveoppgaveoppgave3oppgave3oppgaveoppgaveoppgave2oppgave1.complete () ønsketOutput = " "" Oppgaver - kjøp melk (avhenger av "gå til butikk") - gå til butikk (fullført) - en annen oppgave - underoppgave (avhenger av en annen oppgave) - sub-oppgave (avhenger av 'underoppgave ') "" "taskList.print (). should.equal ønsketOutput
Hva foregår her? Først oppretter vi en ny Oppgaveliste
objekt slik at vi starter fra bunnen av. Deretter oppretter vi fem oppgaver og legger til dem oppgaveliste
. Deretter satte vi opp noen avhengigheter. Til slutt fullfører vi en av våre oppgaver.
Vi bruker CoffeeScript's heredok-syntaks for å lage en multi-line streng. Som du ser, holder vi det ganske enkelt. Hvis en oppgave har en overordnet oppgave, blir det nevnt i parentes etter oppgavenavnet. Hvis en oppgave er fullført, setter vi det også.
Klar til å skrive funksjonen?
print: -> str = "Oppgaver \ n \ n" for oppgave i @tasks str + = "- # task.name" str + = "(avhenger av '# task.parent.name')" hvis task.parent? str + = '(fullført)' hvis oppgavestatus er 'fullstendig' str + = "\ n" str
Det er faktisk ganske greit: vi ser bare over @tasks
array og legg dem til en streng. Hvis de har en forelder, legger vi til det, og hvis de er komplette, legger vi til det også. Legg merke til at vi bruker modifikasjonsskjemaet til hvis
uttalelse, for å stramme opp koden vår. Deretter returnerer vi strengen.
Nå skal alle våre tester passere:
Prøv å legge til noen få funksjoner for å få tak i det hele.
Det er omfanget av vårt lille prosjekt i dag. Du kan laste ned koden fra toppen av denne siden; Faktisk, hvorfor prøver du ikke å legge til noen få funksjoner for å få tak i det hele? Her er noen ideer:
Oppgave
forekomster fra å være avhengige av hverandre (rekursive avhengigheter).Tasklist :: legge
Metoden kaster en feil hvis den mottar noe annet enn en streng eller en Oppgave
gjenstand.I disse dager finner jeg CoffeeScript mer og mer attraktivt, men den største ulempen er at det må kompileres til JavaScript før det er nyttig. Jeg er takknemlig for noe som negerer noe av den arbeidsflytbryteren, og Mokka gjør det definitivt. Selvfølgelig er det ikke perfekt (siden det samler seg til JS før du kjører koden, stemmer ikke linjene i feilene overens med dine CoffeeScript-linjenumre), men det er et skritt i riktig retning for meg!
Hva med deg? Hvis du bruker CoffeeScript, hvordan har du gjort testing? Gi meg beskjed i kommentarene.