Testa din JavaScript med Jasmine

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.


Steg 0: Förstå BDD

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:

  1. Skriv dina test
  2. Titta på dem misslyckas
  3. Gör dem passera
  4. Refactor
  5. Upprepa

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:

  • Fastställa målen för olika intressenter som krävs för att en vision ska genomföras
  • Inblandning av intressenter i genomförandeprocessen genom extern programutveckling
  • Använda exempel för att beskriva beteendet hos applikationen eller av enheter av kod
  • Automatisera dessa exempel för att ge snabb feedback och regressionstestning

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!


Steg 1: Lär dig syntaxen

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.


Steg 2: Ställa in ett projekt

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.

  • Skapa convert.js i src mapp.
  • Skapa convertSpec.js i spec mapp,
  • Kopiera 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.


Steg 3: Skriva testerna

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:

  • Vi gör omvandlingar genom att lagra omvandlingen i ett objekt; Omvandlingsnummer klassificeras efter typ (avstånd, volym, lägg till din egen). För varje mätområde har vi ett basvärde (meter eller liter, här) som allt konverteras till. Så när du ser 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.
  • I vårt fall förväntar vi oss att enheterna som skickats in är samma som nycklarna vi använder i omvandlings tabellen.? Om det här var ett riktigt bibliotek, skulle vi vilja stödja flera format - som "in", "tum" och "inches" - och därför vill vi lägga till lite logik som matchar fromUnit till höger nyckel.
  • I slutet av 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.
  • Om vi ​​inte har 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.


Steg 4: Lära matcharna

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.

toBeDefined / toBeUdedefined

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 (););

toBeTruthy / toBeFalsy

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 (););

toBeLessThan / toBeGreaterThan

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););

att matcha

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 /););

att innehålla

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.

Steg 5: Omslag före och efter

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.

Steg 6: Skriva anpassade matchare

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!


Slutsats: Ha kul själv!

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.