Vi vet alla att vi ska testa vår kod, men det gör vi inte faktiskt. Jag antar att det är rättvist att säga att de flesta av oss släckte det eftersom nio gånger av tio betyder det att lära sig ett annat koncept. I den här handledningen presenterar jag dig en bra liten ram för att testa din JavaScript-kod med lätthet.
Förresten visste du att du kan få dina JavaScript-fel fixade snabbt och enkelt av en expert på Envato Studio?
ThemeManiac, till exempel, kommer att fixaJavaScript-fel eller webbläsarkompatibilitetsproblem på din webbplats eller webbapplikation. Fixerna kan slutföras mycket snabbt, baserat på komplexiteten och tillgänglig information. Han kan också omorganisera dina skript och göra en helt ny användarupplevelse. Han har slutfört mer än 1000 jobb på Envato Studio, med 99% av kunderna som rekommenderar honom.
Idag ska vi lära oss om Jasmine BDD-testramen. Men vi stannar här för en omväg först, att prata mycket kortfattat om BDD och TDD. Om du inte är bekant med dessa akronymer står de för Beteendedriven utveckling och Testdriven utveckling. Jag är mitt i att lära mig om vad var och en av dem är i praktiken och hur de är olika, men här är några av de grundläggande skillnaderna:
BDD och TDD? står för Beteendedriven utveckling och Testdriven utveckling.
TDD i sin enklaste form är just detta:
Det är ganska lätt att förstå, eh?
BDD är lite mer komplex: som jag förstår det just nu tror jag inte att du eller jag som en enskild utvecklare faktiskt kan utöva det fullt ut; det är mer av en lag sak. Här är några av metoderna för BDD:
För att lära dig mer, kan du läsa den omfattande Wikipedia-artikeln (från vilken dessa poäng togs).
Allt detta för att säga, medan Jasmine räknar sig som en BDD-ram, kommer vi att använda den på en mer TDD-stil sätt. Det betyder inte att vi använder det fel, men. När vi är färdiga kan du enkelt testa din JavaScript? och jag förväntar mig att du ska göra det!
Jasmine tar många ledtrådar från Rspec.
Om du är bekant med Rspec, är de facto BDD-ramverket ser du att Jasmine tar många signaler från Rspec. Jasmintester är huvudsakligen två delar: beskriva
block och Det
block. Låt oss se hur det här fungerar.
Vi tittar på några närmare tester i några, men för tillfället kommer vi att hålla det enkelt:
beskriv ('JavaScript tilläggsoperatör', funktion () det ('lägger till två nummer tillsammans', funktion () förvänta (1 + 2). till lika (3);););
Både beskriva
och Det
funktionerna tar två parametrar: en textsträng och en funktion. De flesta testramar försöker läsa så mycket som engelska som möjligt, och du kan se detta med Jasmine. Först märker du att strängen passerade till beskriva
och strängen gick till Det
formulera en mening (av följande slag):? JavaScript tilläggsoperatör lägger till två nummer tillsammans.? Sedan fortsätter vi att visa hur.
Inuti det Det
block, kan du skriva all den inställningskod du behöver för ditt test. Vi behöver inte något för det här enkla exemplet. När du är redo att skriva själva testkoden börjar du med förvänta
funktion, passerar det oavsett vad du testar. Lägg märke till hur det här uttrycker en mening också: vi förväntar oss att 1 + 2 är lika med 3.?
Men jag går före oss själva. Som jag sa, vilket värde du passerar in i förvänta
kommer att testas. Metoden du ringer till, av det returnerade värdet på förvänta
, kommer att bestämmas genom vilket test som körs. Denna grupp av metoder kallas matchare, och vi tittar på flera av dem idag. I det här fallet använder vi toEqual
matcher, som kontrollerar att värdet passerat till förvänta
och värdet gick till toEqual
är samma värde.
Jag tror att du är redo att ta det här till nästa nivå, så låt oss skapa ett enkelt projekt med Jasmine.
Jasmine kan användas av sig själv; eller du kan integrera det med ett Rails-projekt. Vi gör det förra. Medan Jasmine kan springa utanför webbläsaren (Tänk Node, bland annat) kan vi få en riktigt fin liten mall med hämtningen.
Så, gå vidare till den fristående nedladdningssidan och få den senaste versionen. Du borde få något så här:
Du hittar de faktiska Jasmine Framework-filerna i lib
mapp. Om du föredrar att strukturera dina projekt annorlunda, vänligen gör det; men vi kommer att behålla detta för nu.
Det finns faktiskt lite provkod ansluten i den här projektmallen. Den riktiga? JavaScript (koden vi vill testa) finns i src
underkatalog; vi kommer snart att lägga oss. Testkoden - den specs-gå in i spec
mapp. Oroa dig inte för SpecHelper.js
filen just yet; vi kommer tillbaka till det.
Den där SpecRunner.html
filen är vad som kör testen i en webbläsare. Öppna den upp (och kryssrutan? Passerat? I övre högra hörnet), och du borde se något så här:
Detta visar oss att alla prov för provprojektet passerar. När du har läst igenom denna handledning rekommenderar jag att du öppnar spec / PlayerSpec.js
fil och läs den koden. Men just nu, låt oss ge det här testet att skriva grejer ett försök.
convert.js
i src
mapp.convertSpec.js
i spec
mapp,SpecRunner.html
fil och byt namn på den SpecRunner.original.html
.Ta bort länkarna till projektprojektfilerna i SpecRunner.html
och lägg till dessa rader:
Nu är vi redo att skapa ett mini-bibliotek som konverterar mellan mätenheter. Vi börjar med att skriva testerna för vårt mini-bibliotek.
Så, låt oss skriva våra test, ska vi?
beskriv ("Konvertera bibliotek", funktion () beskriv ("distansomvandlare", funktion () ), beskriv ("volymomvandlare", funktion () ););
Vi börjar med detta; vi testar vår Konvertera
bibliotek. Du märker att vi nestar beskriva
uttalanden här. Detta är helt lagligt. Det är faktiskt ett bra sätt att testa separata funktionalitetsbitar av samma kodbas. Istället för två separata beskriva
efterfrågar Konvertera
bibliotekets distansomvandlingar och volymomvandlingar, kan vi få en mer beskrivande serie tester som detta.
Nu på de verkliga testerna. Jag ska upprepa det inre beskriva
ringer här för din bekvämlighet.
beskriva ("distansomvandlaren", funktionen () det ("konverterar tum till centimeter", funktionen () förvänta (Konvertera (12, "in"). till ("cm")) .Equal (30,48);) ; det ("konverterar centimeter till varv", funktion () förvänta (Konvertera (2000, "cm"). till ("yards")) .Equal (21,87);););
Här är våra test för distansomvandlingar. Det är angeläget att märka något här: Vi har inte skrivit en speck code för vår Konvertera
bibliotek, men i dessa tester gör vi mer än bara för att se om det fungerar: Vi bestämmer faktiskt hur det ska användas (och därför implementeras). Så här har vi bestämt oss för att göra våra konverteringar:
Konvertera(, ).till( );
Ja, jag tar en aning från hur Jasmine har genomfört sina test, men jag tycker att det är ett bra format. Så, i dessa två test har jag gjort konverteringarna själv (ok, med en miniräknare) för att se vad resultaten av våra samtal skulle vara. Vi använder toEqual
matcher för att se om våra tester passerar.
Här är volymtest:
beskriva ("volymomvandlare", funktion () det ("konverterar liter till gallon", funktion () förvänta (Konvertera (3, "liter") till ("gallon")) .Equal (0.79);) ; den ("konverterar gallon till koppar", funktion () (förvänta (Konvertera (2, "gallon"). till ("koppar")) .Equal (32);););
Och jag ska lägga till ytterligare två tester på vår toppnivå beskriva
ring upp:
det ("kastar ett fel när det passerade en okänd från-enhet", funktionen () var testFn = funktion () Konvertera (1, "dollar") till ("yen"); förvänta (testFn) .toThrow nytt fel ("okänt från-enhet"));); det ("kastar ett fel när det passerade en okänd enhet", funktionen () var testFn = funktion () Konvertera (1, "cm") till ("furlongs"); förvänta (testFn) .toThrow nytt fel ("okänd till enhet")););
Dessa kontrollerar fel som ska kastas när okända enheter skickas till antingen Konvertera
funktion eller till
metod. Du kommer märka att jag förpackar den faktiska omvandlingen i en funktion och skickar den till förvänta
fungera. Det beror på att vi inte kan ringa funktionen som förvänta
parameter; Vi behöver ge den en funktion och låta den ringa själva funktionen. Eftersom vi måste överföra en parameter till det till
funktion, vi kan göra det på så sätt.
Det andra att notera är att jag introducerar en ny matchare: att kasta
, vilket tar ett felobjekt. Vi ska titta på några fler matchare snart.
Nu, om du öppnar SpecRunner.html
i en webbläsare får du det här:
Bra! Våra test misslyckas. Nu, låt oss öppna vår convert.js
fil och gör lite arbete:
Funktion Konvertera (antal, fromUnit) var omvandlingar = avstånd: meter: 1, cm: 0.01, fot: 0.3048, tum: 0.0254, yards: 0.9144, volym: liter: 1, gallons: 3,785411784, koppar: 0,236588236 , mellanUnit = falskt, typ, enhet; för (skriv in konverteringar) om (omvandlingar (typ)) om ((enhet = omvandlingar [typ] [fromUnit])) betweenUnit = number * unit * 1000; returnera till: funktion (toUnit) if (betweenUnit) för (skriv in konverteringar) if (conversions.hasOwnProperty (typ)) if ((unit = omvandlingar [typ] [toUnit])) fixa (betweenUnit / (enhet * 1000)); kasta nytt fel ("okänd till enhet"); annars kasta nytt fel ("okänt från-enhet"); funktion fix (num) return parseFloat (num.toFixed (2)); ;
Vi kommer inte att diskutera det här, för vi lär oss Jasmine här. Men här är de viktigaste punkterna:
yards: 0,9144
, du vet att det är hur många meter det finns i en meter. Sedan, för att omvandla varv till, säg, centimeter, vi multiplicerar yards
av den första parametern (för att få antalet meter) och sedan dela produkten med centimeter
, antalet meter i en centimeter. På så sätt behöver vi inte lagra konverteringsfrekvensen för varje värdepar. Detta gör det också enkelt att lägga till nya värden senare.fromUnit
till höger nyckel.Konvertera
funktion, lagrar vi mellanvärdet i betweenUnit
, som initialiseras till falsk
. På så sätt, om vi inte har fromUnit
, betweenUnit
kommer vara falsk
går in i till
metod, och så ett fel med kastas.toUnit
, ett annat fel kommer att kastas. Annars delas vi som nödvändiga och returnerar det konverterade värdet.Gå nu tillbaka till SpecRunner.html
och ladda om sidan. Du borde nu se detta (efter kontroll? Visa passerat?):
Varsågod! Våra tester passerar. Om vi utvecklade ett verkligt projekt här skulle vi skriva tester för en viss del av funktionalitet, få dem att passera, skriva tester för en annan kontroll, få dem att passera etc. Men eftersom det här var ett enkelt exempel har vi just gjort det allt i ett föll.
Och nu när du har sett detta enkla exempel på att använda Jasmine, låt oss titta på några fler funktioner som den erbjuder dig.
Hittills har vi använt två matchare: toEqual
och att kasta
. Det finns ju många andra. Här är några som du säkert kommer att hitta användbara; du kan se hela listan på wikien.
Om du bara vill se till att en variabel eller egenskap definieras finns det en matchare för det. Det finns också en som bekräftar att en variabel eller egendom är odefinierad
.
det ("definieras", funktion () var name = "Andrew"; förvänta (namn) .toBeDefined ();) det ("är inte definierat", funktion () var namn; förvänta (namn) .toBeUdedefined (););
Om något ska vara sant eller felaktigt kommer dessa matchare att göra det.
det ("är sant", funktion () förvänta (Lib.isAWeekDay ()). toBeTruthy ();); det ("är falskt", funktion () förvänta (Lib.finishedQuiz) .toBeFalsy (););
För allt talar du folk. Du vet hur dessa fungerar:
det ("är mindre än 10", funktion () förvänta (5) .toBeLessThan (10);); den ("är större än 10", funktion () förvänta (20) .toBeGreaterThan (10););
Har någon utmatningstext som ska matcha ett vanligt uttryck? De att matcha
matcher är redo och villig.
det ("matar ut rätt text", funktion () förvänta (cart.total ()). toMatch (/ \ $ \ d *. \ d \ d /););
Den här är ganska användbar. Den kontrollerar om en array eller sträng innehåller ett objekt eller en substring.
den ("ska innehålla apelsiner", funktion () (förvänta (["äpplen", "apelsiner", "päron"]) .Contain ("apelsiner"););
Det finns också några andra matchare som du kan hitta i wikien. Men vad händer om du vill ha en matchare som inte existerar? Egentligen borde du kunna göra nästan vad som helst med någon uppställningskod och matcharna Jasmine, men ibland är det trevligare att abstrahera en del av den logiken för att få ett mer läsbart test. Serendipitously (ja, faktiskt inte) tillåter Jasmine oss att skapa egna matchare. Men för att göra detta måste vi lära oss lite annat först.
Ofta - när du testar en kodbas - du vill utföra några rader med uppställningskod för varje test i en serie. Det skulle vara smärtsamt och verbalt att kopiera det för alla Det
samtal, så Jasmine har en praktisk liten funktion som gör det möjligt för oss att ange kod att köra före eller efter varje test. Låt oss se hur det här fungerar:
beskriva ("MyObject", funktionen () var obj = ny MyObject (); beforeEach (funktion () obj.setState ("clean");); det ("ändrar tillstånd", funktionen () obj.setState ("smutsigt"), förvänta (obj.getState ()) .Equal ("dirty");) det ("lägger till stater", funktion () obj.addState ("packaged") )) .Evikt (["rent", "förpackat"]);));
I det här konstruerade exemplet kan du se hur, innan varje test körs, tillståndet av obj
är inställd på? ren ?. Om vi inte gjorde det, ändras det ändrade till ett objekt i ett tidigare test som vanligt till nästa test. Självklart kan vi också göra något liknande med AfterEach
fungera:
beskriva ("MyObject", funktion () var obj = nytt MyObject ("clean"); // anger initialt tillstånd efterEach (funktion () obj.setState ("clean");); , funktion () obj.setState ("dirty"); förvänta (obj.getState ()) .Equal ("dirty");) it ("adderar stater", funktion () obj.addState ("packaged" ); förvänta (obj.getState ()) .Equal (["clean", "packaged"]);));
Här ställer vi upp objektet till att börja med och sedan har det rättat efter varje test. Om du vill ha MyObject
funktion så att du kan ge denna kod ett försök, du kan hämta det här i ett GitHub-nät.
Som vi sa tidigare kunde kunden matchare troligen vara till hjälp ibland. Så låt oss skriva en. Vi kan lägga till en matchare i antingen a BeforeEach
ring eller en Det
ringa (ja, jag antar dig skulle kunna gör det i en AfterEach
ring, men det skulle inte ge stor mening). Så här börjar du:
beforeEach (funktion () this.addMatchers (););
Ganska enkelt, va? Vi ringer this.addMatchers
, passerar det en objektparameter. Varje nyckel i det här objektet blir matcharens namn, och den tillhörande funktionen (värdet) blir hur den körs. Låt oss säga att vi vill skapa en matchare som med check för att se om ett nummer är mellan två andra. Här är vad du skulle skriva:
beforeEach (funktion () this.addMatchers (toBeBetween: funktionen (rangeFloor, rangeCeiling) if (rangeFloor> rangeCeiling) var temp = rangeFloor; rangeFloor = rangeCeiling; rangeCeiling = temp; returnera this.actual> rangeFloor && this. faktisk < rangeCeiling; ); );
Vi tar helt enkelt två parametrar, se till att den första är mindre än den andra och returnera ett booleskt uttalande som utvärderar till sant om våra villkor är uppfyllda. Det viktiga att märka här är hur vi får tag på värdet som överfördes till förvänta
fungera: this.actual
.
den ("är mellan 5 och 30", funktion () förvänta (10) .toBeBetween (5, 30);); den ("är mellan 30 och 500", funktion () förvänta (100). tillBeBetween (500, 30););
Det här är vad SpecHelper.js
fil gör den har en beforeEach
samtal som lägger till matcharen tobePlaying ()
. Kolla in det!
Det finns mycket mer du kan göra med Jasmine: Funktionsrelaterade matchare, spioner, asynkrona specifikationer och mer. Jag rekommenderar att du utforskar wikien om du är intresserad. Det finns också några medföljande bibliotek som gör testningen i DOM lättare: Jasmine-jQuery och Jasmine-fixture (som beror på Jasmine-jQuery).
Så om du inte testar JavaScript så här är det nu en utmärkt tid att börja. Som vi har sett gör Jasmines snabba och enkla syntax ganska enkelt. Det finns bara ingen anledning för dig att inte göra det, nu finns det?
Om du vill ta din JavaScript-utveckling vidare, varför inte kolla in utbudet av JavaScript-objekt på Envato Market? Det finns tusentals skript, appar och kodutdrag som hjälper dig.