Sei sulla pagina 1di 63

Instant Testing

with QUnit

(PSOR\48QLWWRLQFUHDVH\RXUHIÀFLHQF\ZKHQWHVWLQJ
JavaScript code

Dmitry Sheiko

BIRMINGHAM - MUMBAI
Instant Testing with QUnit

&RS\ULJKW‹3DFNW3XEOLVKLQJ

$OOULJKWVUHVHUYHG1RSDUWRIWKLVERRNPD\EHUHSURGXFHGVWRUHGLQDUHWULHYDOV\VWHPRU
WUDQVPLWWHGLQDQ\IRUPRUE\DQ\PHDQVZLWKRXWWKHSULRUZULWWHQSHUPLVVLRQRIWKHSXEOLVKHU
H[FHSWLQWKHFDVHRIEULHITXRWDWLRQVHPEHGGHGLQFULWLFDODUWLFOHVRUUHYLHZV

(YHU\HIIRUWKDVEHHQPDGHLQWKHSUHSDUDWLRQRIWKLVERRNWRHQVXUHWKHDFFXUDF\RIWKH
LQIRUPDWLRQSUHVHQWHG+RZHYHUWKHLQIRUPDWLRQFRQWDLQHGLQWKLVERRNLVVROGZLWKRXW
ZDUUDQW\HLWKHUH[SUHVVRULPSOLHG1HLWKHUWKHDXWKRUQRU3DFNW3XEOLVKLQJDQGLWVGHDOHUV
DQGGLVWULEXWRUVZLOOEHKHOGOLDEOHIRUDQ\GDPDJHVFDXVHGRUDOOHJHGWREHFDXVHGGLUHFWO\RU
LQGLUHFWO\E\WKLVERRN

3DFNW3XEOLVKLQJKDVHQGHDYRUHGWRSURYLGHWUDGHPDUNLQIRUPDWLRQDERXWDOORIWKHFRPSDQLHV
DQGSURGXFWVPHQWLRQHGLQWKLVERRNE\WKHDSSURSULDWHXVHRIFDSLWDOV+RZHYHU3DFNW
3XEOLVKLQJFDQQRWJXDUDQWHHWKHDFFXUDF\RIWKLVLQIRUPDWLRQ

)LUVWSXEOLVKHG$XJXVW

3URGXFWLRQ5HIHUHQFH

3XEOLVKHGE\3DFNW3XEOLVKLQJ/WG
/LYHU\3ODFH
/LYHU\6WUHHW
%LUPLQJKDP%3%8.

,6%1

www.packtpub.com
Credits

Author Project Coordinator


Dmitry Sheiko Amey Sawant

Reviewer Proofreader
Sachin G. Kulkarni Stephen Copestake

Acquisition Editor Graphics


Mary Nadar Abhinash Sahu

Commissioning Editor Production Coordinator


Govindan K. Zahid Shaikh

Technical Editor Cover Work


Dylan Fernandes Zahid Shaikh
Sonali S. Vernekar
Cover Image
Copy Editor Valentina D'silva
Brandt D'Mello
Mradula Hegde
About the Author

Dmitry SheikoLVDZHEGHYHORSHUEORJJHUDQGRSHQVRXUFHFRQWULEXWRUOLYLQJDQGZRUNLQJ
LQWKHORYHO\FLW\RI)UDQNIXUWDP0DLQ*HUPDQ\

+HVWDUWHGOHDUQLQJFRPSXWHUSURJUDPPLQJLQWKHODWHHLJKWLHV6LQFHWKHODWHQLQHWLHVKHKDV
EHHQGHYHORSLQJZHEDSSOLFDWLRQVDQGWRROV&XUUHQWO\KHZRUNVDVDVHQLRUZHEGHYHORSHUIRU
WKHOHDGLQJJDPHGHYHORSHUFRPSDQ\&U\WHN*PE+

7KLVLV'PLWU\
VÀUVWERRNEXWKHKDVEHHQEORJJLQJIRUDGHFDGH<RXFDQÀQGKLVWKRXJKWV
DQGVROXWLRQVRQVXFKVXEMHFWVDVFRGHTXDOLW\VRIWZDUHDUFKLWHFWXUH-DYD6FULSW1RGHMV
7\SH6FULSW+70/$3,&66DQG3+3DWwww.dsheiko.com

'PLWU\
VYHU\ÀUVWRSHQVRXUFHFRQWULEXWLRQZDV%&:%DQ;6/7EDVHG&06LQ
6LQFHWKDWWLPHKHKDVEHHQFRQWULEXWLQJTXLWHDFWLYHO\+LVODWHVWZRUNVDUHDYDLODEOH
at https://github.com/dsheiko

,ZRXOGOLNHWRWKDQNP\EHDXWLIXOZLIH2OJDIRUKHUVXSSRUWDQGSDWLHQFH
WKURXJKRXWWKLVSURMHFW
About the Reviewer

Sachin G. KulkarniLVDIUHHODQFHUDQGFRQVXOWDQWZKRKHOSVLQEXLOGLQJVRIWZDUHIRU
EXVLQHVVHVWRDXWRPDWHEXVLQHVVDFWLYLWLHV+HKDVH[WHQVLYHO\ZRUNHGRQ3+30\6TODQG
+70/&66WREULQJRXWWKHEHVW8,GHVLJQDQGLPSOHPHQWEXVLQHVVQHHGV+LVODWHVW
SDVVLRQLVOHYHUDJLQJPRELOHGHYHORSPHQWXVLQJ-DYD6FULSWZLWK3+3EDFNHQGWRSURGXFH
IHDWXUHULFKDSSOLFDWLRQVIRUKLVFOLHQWV6DFKLQXVHVGHYHORSPHQWSDWWHUQVDQGEHVWSUDFWLFHV
WRHQMR\DSSOLFDWLRQGHYHORSPHQWWRLWVIXOOHVWH[WHQW)RUPDQ\\HDUVKHKDVEHHQLQYROYHG
LQRSHQVRXUFHSURMHFWVDQGJLYHVRSHQVRXUFHDGYLFHWRSHRSOH3OHDVHYLVLWKLVEORJDW
sachinkulkarni.infoWRNQRZPRUHDERXWKLP
www.PacktPub.com

6XSSRUWÀOHVH%RRNVGLVFRXQWRIIHUVDQGPRUH
<RXPLJKWZDQWWRYLVLWwww.PacktPub.comIRUVXSSRUWÀOHVDQGGRZQORDGVUHODWHGWR
\RXUERRN

'LG\RXNQRZWKDW3DFNWRIIHUVH%RRNYHUVLRQVRIHYHU\ERRNSXEOLVKHGZLWK3')DQGH3XE
ÀOHVDYDLODEOH"<RXFDQXSJUDGHWRWKHH%RRNYHUVLRQDWwww.PacktPub.com and as a print
ERRNFXVWRPHU\RXDUHHQWLWOHGWRDGLVFRXQWRQWKHH%RRNFRS\*HWLQWRXFKZLWKXVDW
service@packtpub.comIRUPRUHGHWDLOV

At www.PacktPub.com\RXFDQDOVRUHDGDFROOHFWLRQRIIUHHWHFKQLFDODUWLFOHVVLJQXS
IRUDUDQJHRIIUHHQHZVOHWWHUVDQGUHFHLYHH[FOXVLYHGLVFRXQWVDQGRIIHUVRQ3DFNWERRNV
DQGH%RRNV

TM

http://PacktLib.PacktPub.com

'R\RXQHHGLQVWDQWVROXWLRQVWR\RXU,7TXHVWLRQV"3DFNW/LELV3DFNW
VRQOLQHGLJLWDOERRN
OLEUDU\+HUH\RXFDQDFFHVVUHDGDQGVHDUFKDFURVV3DFNW
VHQWLUHOLEUDU\RIERRNV

Why Subscribe?
f )XOO\VHDUFKDEOHDFURVVHYHU\ERRNSXEOLVKHGE\3DFNW
f &RS\DQGSDVWHSULQWDQGERRNPDUNFRQWHQW
f 2QGHPDQGDQGDFFHVVLEOHYLDZHEEURZVHUV

)UHH$FFHVVIRU3DFNWDFFRXQWKROGHUV
,I\RXKDYHDQDFFRXQWZLWK3DFNWDWwww.PacktPub.com\RXFDQXVHWKLVWRDFFHVV
3DFNW/LEWRGD\DQGYLHZQLQHHQWLUHO\IUHHERRNV6LPSO\XVH\RXUORJLQFUHGHQWLDOVIRU
LPPHGLDWHDFFHVV
7DEOHRI&RQWHQWV
Preface 1
Instant Testing with QUnit 7
Setting up QUnit (Simple) 8
Testing assertions (Simple) 12
Writing a custom assertion plugin (Advanced) 19
Testing exceptions (Medium) 21
Testing asynchronous calls (Medium) 24
Organizing test cases (Simple) 28
Using a shared setup (Medium) 31
Testing user actions (Medium) 33
Running QUnit in the console (Advanced) 37
Cross-browser-distributed testing (Advanced) 40
Building a web project (Advanced) 43
QUnit and CI – setting up Jenkins (Advanced) 46
3UHIDFH
-DYD6FULSWZDVÀUVWUHOHDVHGDERXW\HDUVDJR,WKDGQRWEHHQWDNHQVHULRXVO\E\DZLGH
DXGLHQFHIRUPDQ\\HDUV7RGD\LWLVDPDWXUH DQGWKHPRVWSRSXODU SURJUDPPLQJODQJXDJH
:KDWHYHULVGRQHIRUWKH:HERQHZD\RUDQRWKHUUHOLHVRQ-DYD6FULSW)XUWKHUPRUHZLWKWKH
DGYHQWRI+70/UHODWHGWHFKQRORJLHVWKHGHYHORSPHQWIRFXVLVVKLIWLQJHYHQPRUHWRZDUGVD
ULFKLQWHUQHWDSSOLFDWLRQDUFKLWHFWXUH7KDWPHDQVIXUWKHUH[SDQVLRQRIVRSKLVWLFDWHGVFDODEOH
VROXWLRQVEDVHGRQ-DYD6FULSW7KHVHUYHUVLGH-DYD6FULSWLVUDSLGO\JDLQLQJLWVPRPHQWXP
WKHUHE\JLYLQJDQH[WHQVLYHGHPDQGIRUWKHDXWRPDWHGWHVWLQJRI-DYD6FULSW

48QLWLVDIUHDNLVKO\HDV\WRXVH-DYD6FULSWWHVWLQJIUDPHZRUNDWWKHVDPHWLPHLWLVTXLWH
SRZHUIXODQGÁH[LEOHHQRXJKWREHDJRRGFKRLFHIRUXQLWVDQGWKHIXQFWLRQDOWHVWLQJRIDQ\
VFDOHSURMHFWV

7KLVERRNSURYLGHVDVWHSE\VWHSJXLGHWRPDVWHULQJ48QLW,WVKRZVKRZWRVHWXSDQGXVH
48QLWKRZWRDXWRPDWHFURVVEURZVHUWHVWLQJZLWK48QLWDQGKRZWREHQHÀWIURP48QLWLQ
FRRSHUDWLRQZLWKFRQWLQRXVLQWHJUDWLRQWRROV

7KHJRDORIWKLVERRNLVWRKHOSWKHUHDGHUPDNHWKHPRVWRI48QLWLQDVKRUWDPRXQWRIWLPH

What this book covers


Setting up QUnit (Simple)H[SODLQVVHWWLQJXSDWHVWLQJHQYLURQPHQWDQGJHWWLQJDFTXDLQWHG
ZLWKWKH48QLWIUDPHZRUN

Testing assertions (Simple)GHVFULEHVDWXWRULDORQ48QLWDVVHUWLRQPHWKRGVDQGSRSXODU48QLW


DVVHUWLRQSOXJLQV

Writing a custom assertion plugin (Advanced)H[SODLQVOHYHUDJLQJ48QLWFDOOEDFNVIRUDFXVWRP


DVVHUWLRQSOXJLQ
Preface

Testing exceptions (Medium)GHVFULEHVFXVWRPH[FHSWLRQVLQ-DYD6FULSWDQGDVVHUWLQJ


H[FHSWLRQDOEHKDYLRU

Testing asynchronous calls (Medium)GHVFULEHVDVVHUWLQJDV\QFKURQRXVFDOOEDFNVDQG


PRFNLQJ;+5IRULQXQLWWHVWLQJ

Organizing test cases (Simple)H[DPLQHV48QLWIDFLOLWLHVWRNHHSWHVWVORJLFDOO\RUJDQL]HG

Using shared setup (Medium)GHVFULEHVDZD\WRVKDUHWDVNVDPRQJWKHWHVWVRIDJURXSDQG


NHHSLQJWKHWHVWLQJHQYLURQPHQWFOHDQEHWZHHQWHVWH[HFXWLRQV

Testing user actions (Medium)H[SODLQVH[HUFLVLQJIXQFWLRQDOWHVWLQJRQDVLPSOHFDOFXODWRU


DSSOLFDWLRQE\VLPXODWLQJHQGXVHUDFWLRQVDQGDVVHUWLQJLQWHQGHGEHKDYLRU

Running QUnit in the console (Advanced)H[SODLQVUXQQLQJ48QLWWHVWVLQWKHFRPPDQGOLQHE\


WKH3KDQWRP-6KHDGOHVVEURZVHU,WDOVRH[SODLQVDVVLJQLQJ48QLWWHVWLQJWDVNVWR*UXQWDQG
DXWRPDWLQJWHVWLQJZLWK7UDYLV&,

Cross-browser-distributed testing (Advanced)H[SODLQVDXWRPDWLQJFOLHQWVLGHFURVVEURZVHU


WHVWLQJXVLQJWKHFRPPDQGOLQHWRRO%XQ\LS

Building a web project (Advanced)H[SODLQVDGGLQJ48QLWWHVWLQJWDVNVLQWRWKHSURMHFW


EXLOGVFULSW

QUnit and CI – setting up Jenkins (Advanced)H[SODLQVVHWWLQJXSD-HQNLQVFRQWLQXRXV


LQWHJUDWLRQVHUYHUWRDXWRPDWH48QLWWHVWLQJ

:KRWKLVERRNLVIRU
7KLVERRNZRXOGEHKDQG\IRUDQ\RQHZRUNLQJZLWK-DYD6FULSWZKRLVORRNLQJIRUDSRZHUIXO
EXWHDV\WRXVHWHVWLQJIUDPHZRUN

7KHUHDGHUGRHVQ
WQHHGWRNQRZDQ\SDUWLFXODUIUDPHZRUNEXWVKRXOGDWOHDVWNQRZWKHEDVLF
SULQFLSOHVRI-DYD6FULSWDQG+70/

7KHERRNZLOOEHPRVWSURÀWDEOHLIWKHUHDGHUKDVDQ\EDFNJURXQGLQDXWRPDWHGWHVWLQJ

2
Preface

Conventions
,QWKLVERRN\RXZLOOÀQGDQXPEHURIVW\OHVRIWH[WWKDWGLVWLQJXLVKEHWZHHQGLIIHUHQWNLQGVRI
LQIRUPDWLRQ+HUHDUHVRPHH[DPSOHVRIWKHVHVW\OHVDQGDQH[SODQDWLRQRIWKHLUPHDQLQJ

&RGHZRUGVLQWH[WDUHVKRZQDVIROORZV6HWXSWKHEXLOGVFULSWDVbuild.xmlLQWKHURRWRI
WKHSURMHFWZRUNLQJGLUHFWRU\

$EORFNRIFRGHLVVHWDVIROORZV
<?xml version="1.0"?>
<!DOCTYPE project>
<project name="tree" basedir="." default="build">
<target name="build" description="runs QUnit tests using PhantomJS">
<!-- Clean up output directory -->
<delete dir="./build/qunit/"/>
<mkdir dir="./build/qunit/"/>
<!-- QUnit Javascript Unit Tests -->
<echo message="Executing QUnit Javascript Unit Tests..."/>

</target>
</project>

:KHQZHZLVKWRGUDZ\RXUDWWHQWLRQWRDSDUWLFXODUSDUWRIDFRGHEORFNWKHUHOHYDQWOLQHVRU
LWHPVDUHVHWLQEROG
utils.subscribe( global, "load", function() {
var calc = document.getElementById("calc"),
num1 = document.getElementById("num1"),
num2 = document.getElementById("num2"),
sum = document.getElementById("sum");

QUnit.test( "Test that calc sums up typed in numbers by


click", function( assert ){
num1.value = "5";
num2.value = "7";
utils.trigger( calc, "click" );
assert.strictEqual( sum.value, "12" );
});
});

3
Preface

$Q\FRPPDQGOLQHLQSXWRURXWSXWLVZULWWHQDVIROORZV
phantomjs runner.js test-suite.html

New terms and important wordsDUHVKRZQLQEROG:RUGVWKDW\RXVHHRQWKHVFUHHQ


LQPHQXVRUGLDORJER[HVIRUH[DPSOHDSSHDULQWKHWH[WOLNHWKLV(QWHUWKH-HQNLQV
'DVKERDUGSDJHDQGFOLFNRQAdd new jobOLQN

:DUQLQJVRULPSRUWDQWQRWHVDSSHDULQDER[OLNHWKLV

7LSVDQGWULFNVDSSHDUOLNHWKLV

5HDGHUIHHGEDFN
)HHGEDFNIURPRXUUHDGHUVLVDOZD\VZHOFRPH/HWXVNQRZZKDW\RXWKLQNDERXWWKLV
ERRN³ZKDW\RXOLNHGRUPD\KDYHGLVOLNHG5HDGHUIHHGEDFNLVLPSRUWDQWIRUXVWRGHYHORS
WLWOHVWKDW\RXUHDOO\JHWWKHPRVWRXWRI

7RVHQGXVJHQHUDOIHHGEDFNVLPSO\VHQGDQHPDLOWRfeedback@packtpub.comDQG
PHQWLRQWKHERRNWLWOHWKURXJKWKHVXEMHFWRI\RXUPHVVDJH

,IWKHUHLVDWRSLFWKDW\RXKDYHH[SHUWLVHLQDQG\RXDUHLQWHUHVWHGLQHLWKHUZULWLQJRU
FRQWULEXWLQJWRDERRNVHHRXUDXWKRUJXLGHRQwww.packtpub.com/authors

Customer support
1RZWKDW\RXDUHWKHSURXGRZQHURID3DFNWERRNZHKDYHDQXPEHURIWKLQJVWRKHOS\RX
WRJHWWKHPRVWIURP\RXUSXUFKDVH

'RZQORDGLQJWKHH[DPSOHFRGH
<RXFDQGRZQORDGWKHH[DPSOHFRGHÀOHVIRUDOO3DFNWERRNV\RXKDYHSXUFKDVHGIURP\RXU
account at http://www.packtpub.com,I\RXSXUFKDVHGWKLVERRNHOVHZKHUH\RXFDQ
visit http://www.packtpub.com/supportDQGUHJLVWHUWRKDYHWKHÀOHVHPDLOHGGLUHFWO\
WR\RX

4
Preface

Errata
$OWKRXJKZHKDYHWDNHQHYHU\FDUHWRHQVXUHWKHDFFXUDF\RIRXUFRQWHQWPLVWDNHVGR
KDSSHQ,I\RXÀQGDPLVWDNHLQRQHRIRXUERRNV³PD\EHDPLVWDNHLQWKHWH[WRUWKH
FRGH³ZHZRXOGEHJUDWHIXOLI\RXZRXOGUHSRUWWKLVWRXV%\GRLQJVR\RXFDQVDYHRWKHU
UHDGHUVIURPIUXVWUDWLRQDQGKHOSXVLPSURYHVXEVHTXHQWYHUVLRQVRIWKLVERRN,I\RXÀQG
DQ\HUUDWDSOHDVHUHSRUWWKHPE\YLVLWLQJhttp://www.packtpub.com/supportVHOHFWLQJ
\RXUERRNFOLFNLQJRQWKHerrata submission formOLQNDQGHQWHULQJWKHGHWDLOVRI\RXU
HUUDWD2QFH\RXUHUUDWDDUHYHULÀHG\RXUVXEPLVVLRQZLOOEHDFFHSWHGDQGWKHHUUDWDZLOOEH
XSORDGHGWRRXUZHEVLWHRUDGGHGWRDQ\OLVWRIH[LVWLQJHUUDWDXQGHUWKH(UUDWDVHFWLRQRI
WKDWWLWOH

Piracy
3LUDF\RIFRS\ULJKWPDWHULDORQWKH,QWHUQHWLVDQRQJRLQJSUREOHPDFURVVDOOPHGLD$W3DFNW
ZHWDNHWKHSURWHFWLRQRIRXUFRS\ULJKWDQGOLFHQVHVYHU\VHULRXVO\,I\RXFRPHDFURVVDQ\
LOOHJDOFRSLHVRIRXUZRUNVLQDQ\IRUPRQWKH,QWHUQHWSOHDVHSURYLGHXVZLWKWKHORFDWLRQ
DGGUHVVRUZHEVLWHQDPHLPPHGLDWHO\VRWKDWZHFDQSXUVXHDUHPHG\

3OHDVHFRQWDFWXVDWcopyright@packtpub.comZLWKDOLQNWRWKHVXVSHFWHGSLUDWHGPDWHULDO

:HDSSUHFLDWH\RXUKHOSLQSURWHFWLQJRXUDXWKRUVDQGRXUDELOLW\WREULQJ\RXYDOXDEOHFRQWHQW

Questions
<RXFDQFRQWDFWXVDWquestions@packtpub.comLI\RXDUHKDYLQJDSUREOHPZLWKDQ\
DVSHFWRIWKHERRNDQGZHZLOOGRRXUEHVWWRDGGUHVVLW

5
Instant Testing
with QUnit
:HOFRPHWR,QVWDQWWHVWLQJZLWK48QLW

)RUDORQJWLPH-DYD6FULSWZDVQ
WWDNHQVHULRXVO\+RZHYHUZLWKWKHDGYHQWRI$-$;
LWWXUQHGRXWWKDWXVLQJ-DYD6FULSWZHESDJHVFDQEHEURXJKWWRVSHHGZLWKWKHGHVNWRS
VRIWZDUH5,$5LFK,QWHUQHWDSSOLFDWLRQ http://en.wikipedia.org/wiki/Rich_
Internet_application 7KHIRUWKFRPLQJVHUYHUVLGH-DYD6FULSWSURYHGWKDWDSSOLFDWLRQV
FDQEHZULWWHQHQWLUHO\LQ-DYD6FULSW-DYD6FULSWEDVHGDSSOLFDWLRQVVWDUWHGJURZLQJXSDQG
JHWWLQJFRPSOH[(YHQLIJRRGGHYHORSHUVZRUNRQWKHPDQ\VRSKLVWLFDWHGVRIWZDUHZLOOVWLOO
KDYHGHIHFWV7HVWHQJLQHHUVGRWKHLUEHVWWRFDWFKWKHVHEHIRUHWKHSURGXFWLVUHOHDVHG
EXWLWLVKDUGO\SRVVLEOHWRGHWHFWDOOWKHÁDZVPDQXDOO\$XWRPDWHGWHVWLQJLVWKHEHVWZD\
WRLQFUHDVHWKHHIÀFLHQF\DQGFRYHUDJHRIVRIWZDUHWHVWLQJ%HVLGHVLWUHYHDOVDQ\SUREOHP
SUHVHQWLQWKHHDUO\GHYHORSPHQWVWDJHVZKLFKFXWVGRZQWKHFRVWRIÀ[LQJLWGUDVWLFDOO\

5HJDUGOHVVRIWKHWHFKQRORJLHVXVHGWKHEHVWGHYHORSPHQWSUDFWLFHVLQFOXGHDXWRPDWLRQ
RIXQLWWHVWVLQWHJUDWLRQWHVWVV\VWHPWHVWVDQGIXQFWLRQDOWHVWV8QLWWHVWVYHULI\LIWKH
VPDOOHVWIXQFWLRQDOSDUWVRIWKHDSSOLFDWLRQZRUNDVLQWHQGHGLQLVRODWLRQ,QWHJUDWLRQWHVWV
FKHFNLIWKHFRPSRQHQWVRIWKHSURMHFWFROODERUDWHSURSHUO\6\VWHPWHVWVH[DPLQHWKHZKROH
DSSOLFDWLRQ³XVXDOO\IRUFULWHULDVXFKDVXVDELOLW\SHUIRUPDQFHORDGVWUHVVVFDODELOLW\
FRPSDWLELOLW\DQGVHFXULW\³DQGIXQFWLRQDOWHVWVYDOLGDWHWKDWWKHDSSOLFDWLRQ8,LVÀQHIURP
WKHXVHU
VSHUVSHFWLYH

&XUUHQWO\WKHUHDUHSOHQW\RIWRROVPHDQWWRDFKLHYHDXWRPDWHGWHVWLQJLQ-DYD6FULSW
2QHRIWKHPRVWSRSXODUWHVWLQJIUDPHZRUNVLV48QLW,W
VVXIÀFLHQWO\SRZHUIXOH[WUHPHO\
ÁH[LEOHDQGDWWKHVDPHWLPHVXUSULVLQJO\HDV\WRVWDUWZLWK
Instant Testing with QUnit

7KLVLVDSUDFWLFDOERRNVKRZLQJKRZWRDFKLHYHDEHQHÀFLDODXWRPDWHGWHVWLQJRI\RXU
-DYD6FULSWDSSOLFDWLRQVZLWK48QLWDQGLWVSOXJLQVKRZWRHQJDJH48QLWIRUDXWRPDWHG
FURVVEURZVHUWHVWLQJDQGKRZWRXWLOL]H48QLWLQFRQMXQFWLRQZLWKGHYHORSHUDXWRPDWLRQ
DQGFRQWLQXRXVLQWHJUDWLRQWRROV

7KHERRNSURYLGHVIUDPHZRUNDJQRVWLF9DQLOOD-6H[DPSOHVRQDOOWKHFRQWULEXWHGWDVNV
WKDWRQHFDQÀQGHDV\WRIROORZ7KHVRXUFHFRGHLVFDWHJRUL]HGE\WKHWDVNVDQGDYDLODEOH
IRUGRZQORDGDWhttp://www.packtpub.com/support7KXVZKDWHYHU\RXUSUHYLRXV
H[SHULHQFHRUZKDWHYHUOLEUDULHV\RXZRUNZLWK\RXFDQPDVWHUWKHWDVNVLQQRWLPH

6HWWLQJXS48QLW 6LPSOH
,QWKLVUHFLSHZHZLOOGRZQORDGWKH48QLWWHVWLQJIUDPHZRUNFRPSRQHQWVDQGFUHDWHDWHVW
UXQQHU+70/:HZLOOH[DPLQHKRZWKH48QLWWHVWUXQQHUUHSRUWVRQFDVHVRIERWKVXFFHHGHG
DQGIDLOHGWHVWV:HZLOODOVRFRQVLGHUVRPHRIWKHEHVWSUDFWLFHVRIWKHIUDPHZRUNÀOHVWUXFWXUH

Getting ready
7KHIUDPHZRUNFRQVLVWVRIWKH48QLW-DYD6FULSWPRGXOHWKHWHVWUXQQHU+70/DQGWKHVW\OH
VKHHW7HVWUXQQHULVDWHVWVXLWHH[HFXWHU,WORDGVRWKHU48QLWFRPSRQHQWVDQGJHQHUDWHVWKH
UHSRUWSDJH:HLQFOXGHRXUWHVWVHLWKHUGLUHFWO\LQWKHWHVWUXQQHU+70/RULQDQH[WHUQDOÀOH
7KHFRPSRQHQWPRGHOLVLOOXVWUDWHGDVIROORZV

Test runner qunit.css

Report qunit.js

tests.js

:HFDQVLPSO\FRS\SDVWHWKHWHVWUXQQHUFRGHIURPwww.qunitjs.comDVZHOODV
GRZQORDGLQJWKH&66DQG-DYD6FULSWFRPSRQHQWV,QIDFWLWFDQZRUNZLWKRXWGRZQORDGLQJE\
XVLQJWKHIROORZLQJOLQNVWRM4XHU\&'1LQWKHWHVWUXQQHU

f http://code.jquery.com/qunit/qunit-git.css
f http://code.jquery.com/qunit/qunit-git.js

8
Instant Testing with QUnit

How to do it
:HOOKRZGRHVLWZRUN"

 Create an index.htmlÀOHZLWKWKHIROORZLQJFRQWHQW


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Test Suite</title>
<link rel="stylesheet" href="http://code.jquery.com/qunit/
qunit-git.css" type="text/css" media="screen">
</head>
<body>
<h1 id="qunit-header">Test Suite</h1>
<h2 id="qunit-banner"></h2>
<div id="qunit-testrunner-toolbar"></div>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>
<div id="qunit-fixture">test markup</div>

<div id="sandbox">
<!--Put here sandbox HTML if needed -->
</div>

<script type="text/javascript" src="http://code.jquery.com/


qunit/qunit-git.js"></script>
<script type="text/javascript">
QUnit.test( "Test example", function( assert ){
assert.ok( true );
});
</script>
</body>
</html>

9
Instant Testing with QUnit

 /RDG+70/LQ\RXUEURZVHU<RXZLOOVHHVRPHWKLQJVLPLODUWRWKH
IROORZLQJVFUHHQVKRW

6RZKDWLVRQWKHSDJH")LUVWLVWKHWLWOHWKDWZHDVVLJQHGLQWKH+70/8QGHUQHDWK
IROORZVDEDUWKDWLVLQFDOPLQJJUHHQLIDOOWKHWHVWVVXFFHHGHGDQGDOHUWLQJUHGLIDQ\
IDLOHG,QFDVHWKHWHVWVIDLOZHZLOOVHHVRPHWKLQJVLPLODUWRWKHIROORZLQJVFUHHQVKRW

)XUWKHUZHFDQVHHDWRROER[,WFRQWDLQVWKHIROORZLQJRSWLRQV
‰ 7KHHide passed testsFKHFNER[XVHIXOZKHQ\RXKDYHSOHQW\RIWHVWV
UXQQLQJDQGZDQWWRIRFXVRQO\RQWKRVHWKDWIDLOHG
‰ 7KHCheck for GlobalsFKHFNER[ZKLFKDOORZV\RXWRWHVWLIWKHJOREDOREMHFW
ZDVPRGLILHGGXULQJWHVWLQJ

10
Instant Testing with QUnit

‰ 7KHNo try-catchFKHFNER[ZKLFKWULJJHUVWKHUXQQLQJRIWKHWHVWVRXWVLGH
WKHWU\FDWFKEORFNUHYHDOLQJQDWLYHH[FHSWLRQV7KLVLVKDQG\ZKLOHGHEXJJLQJ
RQOHJDF\EURZVHUV

5LJKWEHORZWKHWRROER[48QLWGLVSOD\VWKHXVHUDJHQWGHWDLOV$IWHUWKDWZHKDYH
DUHSRUWDUHDZLWKWKHRYHUDOOVWDWLVWLFVDQGDOLVWRISHUWHVWUHVXOWV

How it works
<RXPD\OLNHWRNQRZZKDWUHDOO\KDSSHQVEHKLQGWKHVFHQHV:KLOHWKH48QLW-DYD6FULSW
PRGXOHLVEHLQJSURFHVVHGLWVXEVFULEHVDKDQGOHURQWKHwindow.onloadHYHQW
7KHKDQGOHULQLWLDOL]HVWKHQUnitREMHFWDQGSRSXODWHVWKHSODFHKROGHUVRIWKHWHVWUXQQHU
OD\RXWZLWKWKHHOHPHQWVUHTXLUHGWREXLOGWKHUHSRUWSDJH7RZDUGVWKHHQGLWFDOOVWKH
QUnit.startPHWKRGZKLFKDFWXDOO\UXQVWHVWVDYDLODEOHDWWKDWWLPH7KXVE\GHIDXOW
48QLWWULHVWRUXQWHVWVDVVRRQDVWKHSDJH'20LVUHDG\,I\RXZRUNZLWKPRGXODU-DYD6FULSW
VXFKDV$0'DQG&RPPRQ-6DOOWKHPRGXOHVLQFOXGLQJWHVWVDUHORDGHGDV\QFKURQRXVO\
7KHFDVHUHTXLUHVWKHWHVWUXQQHUWREHPDQXDOO\VWDUWHGZKLFKFDQEHSHUIRUPHGE\XVLQJ
DFRQÀJXUDWLRQRSWLRQDVGHVFULEHGLQWKHIROORZLQJFRGH
QUnit.config.autostart = false;
require(
[ "test-module1", "test-module2" ],
function() {
QUnit.start();
}
);

There's more
,QWKHSUHYLRXVH[DPSOHZHSODFHGWKHGXPP\WHVWFRGHVWUDLJKWLQWRWKHWHVWUXQQHU
+70/7KLVZRUNVÀQHIRUDVKRUWH[DPSOHEXWGRHVQ
WVXLWDUHDOSURMHFWZLWKDORWRIWHVWV
<RXZLOOKDYHWRFRPHXSZLWKDGHFHQWÀOHVWUXFWXUH,ZRXOGUHFRPPHQGFUHDWLQJDGHGLFDWHG
VXEGLUHFWRU\ WHVWV LQ\RXUSURMHFW
Vwwwroot7KHUH\RXFDQDGGVXEGLUHFWRULHVSHUSURMHFW
PRGXOHXQGHUWHVW7KHVHVXEGLUHFWRULHVFRQWDLQWKHFRGHIRUPRGXOHWHVWVDQGWHVWUHODWHG
ÀOHVLIUHTXLUHG

8VXDOO\WHVWUXQQHULVVLPSO\QDPHGindex.htmlDVLWDJJUHJDWHVDOOWKH48QLWWHVWV
RIWKHSURMHFW,ZRXOGHQFDSVXODWHGLIIHUHQWNLQGVRIWHVWVLQWRUHVSHFWLYHUXQQHUVVXFK
as unit-tests.htmlfunctional-tests.htmlDQGacceptance-tests.html

11
Instant Testing with QUnit

7KXVZHFDQUXQIDVWXQLWWHVWVZLWKHYHU\FRPPLW IRUH[DPSOHE\D&RQWLQXRXV,QWHJUDWLRQ
VHUYHU DQGUXQIXQFWLRQDODQGDFFHSWDQFHWHVWVEHIRUHDQ\GHSOR\PHQW,WDOVRLPSURYHVWHVW
UHDGDELOLW\ZKHQ\RXNHHSIXQFWLRQDOWHVWVRXWRIDFFHSWDQFHWHVWV
app-wwwroot/
├── js/
│ └── moduleA.js
└── tests/
├── moduleA/
│ ├── dummies.js
│ ├── stubs.js
│ ├── mocks.js
│ ├── fixtures.js
│ └── unit-tests.js
└── unit-tests.html

7HVWLQJDVVHUWLRQV 6LPSOH
$VVHUWLRQLVWKHPDLQPHWKRGXVHGLQWHVWLQJ,QWKLVUHFLSHZHZLOOH[DPLQHDOOWKHDVVHUWLRQ
PHWKRGVWKDW48QLWKDV%HVLGHVZHZLOOWDNHDORRNDWDIHZDVVHUWLRQPHWKRGVSURYLGHGE\
SRSXODU48QLWSOXJLQV

Getting ready
,WZRXOGEHPXFKPRUHXVHIXOLIZKLOHOHDUQLQJDVVHUWLRQVZHWHVWHGWKHUHDOFRGH
7KHIROORZLQJWZRKHOSHUIXQFWLRQVDUHTXLWHVXLWDEOHWRVKRZGLVFUHWHFDVHVRIWKHXVH
RIDVVHUWLRQV
var utils = (function() {
"use strict";
return {
/**
* Port of PHP trim function which differ from EcmaScript 5
String.prototype.trim
* Strip whitespace (or other characters) from the beginning and
end of a string
* Without the second parameter, trim() will strip these
characters:
* " " (ASCII 32 (0x20)), an ordinary space.
* «\t» (ASCII 9 (0x09)), a tab.
* "\n" (ASCII 10 (0x0A)), a new line (line feed).
* "\r" (ASCII 13 (0x0D)), a carriage return.
* "\0" (ASCII 0 (0x00)), the NUL-byte.
* "\x0B" (ASCII 11 (0x0B)), a vertical tab.

12
Instant Testing with QUnit
* @param {string} str
* @param {string} charlist
* @return {string}
*/
trim: function( str, charlist ) {
charlist = charlist || " \t\n\r\0\x0B";
return str.replace( new RegExp( "^[" + charlist + "]+|[" +
charlist + "]+$", "g" ), '' );
},
/**
* Emulate class-based inheritance.
* <example>
* ClassA = function() {}
* ClassB = utils.extendClass(ClassA, {
* prop: true
* });
* </example>
*/
extendClass: function( SuperType, subType ) {
var prop,
F = function(){};
F.prototype = new SuperType();
F.prototype.constructor = SuperType;
for ( prop in subType ) {
if ( subType.hasOwnProperty( prop ) ) {
F.prototype[ prop ] = subType[ prop ];
}
}
return F;
}
};
}( this ));

7KHÀUVWRQHLVDQDQDORJRIWKH3+3trimIXQFWLRQ,WUHPRYHVDQ\ZKLWHVSDFHVIURP
WKHEHJLQQLQJDQGHQGRIDVXSSOLHGVWULQJ7KHVHFRQGIXQFWLRQLPSOHPHQWVFODVVEDVHG
LQKHULWDQFH6R\RXFDQFUHDWHIRUWKLVIXQFWLRQDQHZFODVV constructor E\H[WHQGLQJ
DJLYHQRQH SuperType ZLWKDVSHFLÀHGREMHFW subType 

How to do it
 'HÀQHWKHWHVWVFRSHZLWKWKHUHTXLUHGDVVHUWLRQVDVIROORZV
QUnit.test( "Test title", function( assert ) {
assert.<assertion-method>( comparison of actual and expected
values, "assertion title" );
});

13
Instant Testing with QUnit

+HUHWKHVFRSHRIWKHWHVWLVGHÀQHGWKDWFRPSULVHVRQHRUPRUHDVVHUWLRQV
48QLWSURYLGHVDVVHUWLRQPHWKRGVVXFKDVequalnotEqualstrictEqual
notStrictEqualokdeepEqualDQGnotDeepEqual

<RXPD\ILQGSOHQW\RIH[DPSOHVPDGHZLWKWKHVLPSOLILHGLQWHUIDFHRQ
WKH,QWHUQHW48QLWPHWKRGVDUHRIFRXUVHQDPHVSDFHGEXWWKH\DUH
DOVRH[SRVHGDVVKRUWFXWIXQFWLRQVRQWKHJOREDOREMHFW6R\RXFDQSXW
WKHFRGHLQWKHIROORZLQJZD\
test( "Test title", function() {
<assertion-method>( comparison of actual and
expected values, "assertion title" );
});
+RZHYHU,ZRXOGUDWKHUUHFRPPHQGDJDLQVWLW$V\RXVHHLW
VKDUGO\
VKRUWRIFRGHEXWLWPD\LQWHUIHUHZLWKWKHJOREDOSURSHUWLHVDGGHGE\
RWKHUVFULSWVDQG\RXZRQ
WEHDEOHWRDFFHVVSOXJLQDVVHUWLRQVWKLVZD\

 Test utils.trimZLWKHTXDOLW\DVVHUWLRQV,IZHSDVVDVWULQJDVDQDUJXPHQWWR


WKHutils.trimIXQFWLRQIURPWKHSUHFHGLQJH[DPSOHZHH[SHFWWKLVVWULQJZLWKDOO
OHDGLQJDQGWUDLOLQJZKLWHVSDFHVUHPRYHGLQWKHRXWSXW,IWKHZKLWHVSDFHVVWLOOH[LVW
WKHIXQFWLRQGRHVQ
WZRUNDVLQWHQGHG7KLVFDQEHWHVWHGE\DSSO\LQJWKHFRPPRQ
SDWWHUQDVIROORZV
QUnit.test( "Test utils.trim function", function( assert ) {
assert.eq ual( utils.trim(" .. "), "..", "trims spaces" );
assert.notEqual( utils.trim(" .. "), " .. ", "does not ignore
trimming" );
});

 Test utils.trimZLWKVWULFWHTXDOLW\DVVHUWLRQV6XUHO\ZHFDQ


WWHVWSURSHUO\
WKLVIXQFWLRQZLWKRQO\RQHRUWZRDVVHUWLRQVEXWQHYHUWKHOHVVWKHFDVHVKRZV
\RXFDQXVHWKHequal and notEqualPHWKRGV7KHPHWKRGVstrictEqual
and notStrictEqualDFWLQWKHVDPHZD\DVWKHODVWWZRH[FHSWWKDWWKH\FKHFN
IRUHTXDOLW\PRUHVWULFWO\ OHW
VVD\LQDSURSHUZD\ 7KDW
VDOODERXWWKHGLIIHUHQFH
EHWZHHQ== and ===-DYD6FULSWFRPSDULVRQRSHUDWRUV7KHÀUVWRQHLVNQRZQDVEDG
SDUW-DYD6FULSW 6HHJavaScript: The Good Parts, Douglas Crockford, O'Reilly Media 
DQGVKRXOGEHDYRLGHGXQOHVVRQHUHDOO\PHDQVWUXWK\IDOV\FRPSDULVRQ7KXVZH
UDWKHUUHZULWHWKHutils.trimWHVWDVSHUWKHIROORZLQJFRGH
QUnit.test( "Test utils.trim function", function( assert ) {
assert.strictEqual( utils.trim(" .. "), "..", "trims spaces" );
assert.notStrictEqual( utils.trim(" .. "), " .. ", "does not
ignore trimming" );
});

14
Instant Testing with QUnit

 ([DPLQHWKHULVNVRIXVLQJZHDNHTXDOLW\DVVHUWLRQV
assert.notEqual( " \n\r\t ", false ); // fails
assert.notStrictEqual( " \n\r\t ", false ); // passes

 Test utils.extendClassZLWKERROHDQDVVHUWLRQV)LUVWZHKDYHWRZULWHDFOLHQW


FRGHIRUWKHPHWKRG:HGHÀQHDSVHXGRFODVV FRQVWUXFWRUZLWKVRPHPHPEHUV
DGGHGWRWKHSURWRW\SH DQGSDVVLWWRutils.extendClass:HQHHGWRWHVWLWLI
WKHGHULYHGSVHXGRFODVVPDNHVDQLQVWDQFHRIZKDWZHH[WHQGHG:HFDQQRWXVH
HTXDOLW\DVVHUWLRQPHWKRGVKHUHEXW48QLWSURYLGHVWKHokDVVHUWLRQPHWKRGZKLFK
YHULÀHVLIDVXSSOLHGH[SUHVVLRQLVWUXHDQGLWFDQOLNHO\EHXVHG
QUnit.test( "Test utils.extendClass function", function( assert )
{
var ClassA = function() {
this.propA = true;
},
ClassB = utils.extendClass( ClassA, {
propB: true
}),
ClassC = utils.extendClass( ClassB, {
propC: true
}),
obj = new ClassC();

assert.ok( obj instanceof ClassA, "it is an instance of ClassA"


);
assert.ok( obj instanceof ClassB, "it is an instance of ClassB"
);
assert.ok( obj instanceof ClassC, "it is an instance of ClassC"
);

});

 7HVWWKHWZRLGHQWLFDOREMHFWVZLWKGHHSHTXDOLW\DVVHUWLRQV$WWKLVSRLQWZHKDYH
WULHGWKHHQWLUHFRUH48QLWDVVHUWLRQPHWKRGV+RZHYHU48QLW$3,DOVRLQFOXGHV
KHOSHUPHWKRGVdeepEqual and notDeepEqualWKDWDOORZ\RXWRDVVHUWRQWKH
HTXDOLW\RIFRPSOH[W\SHVVXFKDVDUUD\VREMHFWVUHJXODUH[SUHVVLRQVGDWHVDQG
IXQFWLRQV7KLVLVXVHIXOZKHQ\RXQHHGWRFRPSDUHWZRLGHQWLFDOREMHFWV(TXDOLW\
PHWKRGV equalstrictEqual ZLOOQHYHUFRQÀUPWKHLGHQWLFDOREMHFWVXQOHVV
WKHVHDUHQRWUHIHUHQFHVWRWKHVDPHREMHFW2QWKHFRQWUDU\deepEqual recursively
FRPSDUHVPHPEHUVRIERWKWKHVXSSOLHGW\SHV7KHIROORZLQJFRGHLVDQH[DPSOHRI
WKHDVVHUWLRQWKDWWZRDUUD\VDUHLGHQWLFDO
QUnit.test( "Examine deepEqual", function( assert ) {
var dummy1 = {
propA: true,
propB: true

15
Instant Testing with QUnit
},
dummy2 = {
propA: true,
propB: true
};

assert.notStrictEqual( dummy1, dummy2, "that is not refernces on


the same object" );
assert.deepEqual( dummy1, dummy2, "but they are identical" );
});

There's more
%HVLGHV48QLWFRUHDVVHUWLRQPHWKRGV\RXFDQDOVRXVHWKHPHWKRGVSURYLGHGE\QXPHURXV
48QLWSOXJLQV:HZLOOH[DPLQHWKHPRVWSRSXODURQHVKHUH

0RVW[8QLWIUDPHZRUNVKDYHIDFLOLWLHVIRUVRFDOOHGParameterized Unit Tests PUTs 7KHVH


WHVWVDUHSURYLGHGZLWKDVHWRIDQ\QXPEHURIWHVWLQSXWYDOXHV'XULQJWHVWH[HUFLVLQJWKHVH
YDOXHVDUHSDVVHGUHSHDWHGO\DVSDUDPHWHUVWRWKHWHVW7KXVWKHXQLWLVWHVWHGZLWKDOOWKH
QHFHVVDU\LQSXWFDVHV7KHParameterizeSOXJLQ https://github.com/AStepaniuk/
qunit-parameterize EULQJVWKHVHIDFLOLWLHVWR48QLW

'R\RXUHPHPEHUZHZHUHWHVWLQJWKHutils.trimIXQFWLRQ"7RPDNHVXUHWKHVWULQJLV
WULPPHGFRUUHFWO\LQPDQ\FDVHVZHZRXOGQHHGWRZULWHDORWRIDVVHUWLRQVUHSHDWLQJDOPRVW
WKHVDPHFRGHDOORYHUWKHWHVWVFRSH,WFDQEHVLPSOLÀHGQRWDEO\E\XVLQJWKH3DUDPHWHUL]H
SOXJLQ6HHWKHIROORZLQJH[DPSOH
QUnit
.cases([
{ str: ' string ', charlist: null, expected : 'string' },
{ str: ' string ', charlist: null, expected : 'string'
},
{ str: '\t\n\rstring ', charlist: null, expected :
'string' },
{ str: '||string|', charlist: "|", expected : 'string' },

])
.test("Test trim", function( params ) {
var expected = utils.trim( params.str, params.charlist );
strictEqual( expected, params.expected );
});

,I\RXQHHGWRDVVHUWWKDWWZRÁRDWQXPEHUVDUHHTXDOWRVRPHGHJUHHLWZRXOGEHQRWVRHDV\
WRDFKLHYHWKLVLQ-DYD6FULSW7KDWLVDQRWKHUEDGSDUWRIWKHODQJXDJH&RQVLGHUWKHIROORZLQJ
FRGHVQLSSHW

16
Instant Testing with QUnit
var a = 1.1234,
b = 1.1230;

console.log( ( Math.round( parseFloat( a ) * 1000 ) / 1000 ) ===


( Math.round( parseFloat( b ) * 1000 ) / 1000 ) );

2QWKHRWKHUKDQGWKHCloseSOXJLQ https://github.com/JamesMGreene/qunit-
assert-close SURYLGHVDPXFKPRUHFRQYHQLHQWZD\WRGRWKLVDVVKRZQLQWKH
IROORZLQJFRGH
assert.close(actual, expected, maxDifference, message);
assert.notClose(actual, expected, minDifference, message);

Here actual and expectedDUHWKHÁRDWQXPEHUVZHFRPSDUHmaxDifference


minDifferenceDUHWKHGHJUHHWRZKLFKZHFRPSDUHWKHPDQG message is an optional
GHVFULSWLRQRIWKHDVVHUWLRQ6RWKHWHVWPD\ORRNOLNHWKHIROORZLQJFRGH
(function(){
'use strict';
var closeToPi = 3.141,
notCloseToPi = 3.1;

test('Test closeToPi is close to PI till 1/1000', function( assert


) {
assert.close( closeToPi, Math.PI, 0.001 );
});
test('Test notCloseToPi isn\'t close to PI till 1/1000', function(
assert ) {
assert.notClose( 3.1, Math.PI, 0.001 );
});

}( this ));

6RPHWLPHVZHKDYHWRPDNHVXUHWKDWIXQFWLRQVDUHEHLQJLQYRNHGLQDGHÀQHGRUGHU
IRUH[DPSOHWKH$0'FRPSDWLEOHPRGXOHORDGHUZRUNVDV\QFKURQRXVO\EXWPXVWUHVROYH
GHSHQGHQFLHVZKHQWKH\DUHVHTXHQFHG7KHStepSOXJLQ https://github.com/
JamesMGreene/qunit-assert-step DOORZVXVWRDVVHUWWKHSURSHUVHTXHQFHLQZKLFK
WKHFRGHVKRXOGSHUIRUP7KHIROORZLQJH[DPSOHYHULÀHVWKDWWKHbar function is called prior to
WKHfooIXQFWLRQ
(function(){
'use strict';
test('Test "foo" is invoked after "bar"', function( assert ) {
function foo() {
assert.step( 2 );
}
function bar() {

17
Instant Testing with QUnit
assert.step( 1, "bar is invoked first" );
}
bar();
foo();
});
}( this ));

1RZDGD\VWKHFDQYDVHOHPHQWLVZLGHO\XVHGWRUHQGHUGLDJUDPVVLPSOHDQLPDWLRQ
DQGEURZVHUEDVHGJDPHV2EYLRXVO\LWPHDQVDORWRIFRGHWHVWLQJ+RZHYHUDQ\DVVHUWLRQ
RQDFDQYDVUHJLRQGRHVQ
WVHHPDQHDV\WDVN7KHCanvasSOXJLQ https://github.com/
JamesMGreene/qunit-assert-canvas SURYLGHVWKHDVVHUWLRQPHWKRGWRWHVWLQGLYLGXDO
SL[HOYDOXHVLQDFDQYDV,QWKHIROORZLQJH[DPSOHZHGHÀQHDFDQYDVDUHDRQWKHSDJHDQG
GUDZDEODFNUHFWDQJOH7KHQZHDVVHUWDGRWRQWKHUHFWDQJOHWKDWKDVEODFNFRORU
<div id="sandbox"><canvas width="5" height="5"></canvas></div>
<script type="text/javascript" src="http://code.jquery.com/qunit/
qunit-git.js"></script>
<script type="text/javascript" src="../vendors/Canvas/qunit-assert-
canvas.js"></script>
<script type="text/javascript">
(function( window ){
'use strict';
var document = window.document,
canvas = document.getElementById('sandbox').firstChild,
context = canvas.getContext('2d');

test('Test the dot at 0,0 is black', function( assert ) {


context.fillStyle = 'rgba(0, 0, 0, 0)';
context.fillRect( 0, 0, 10, 10 );
assert.pixelEqual( canvas, 0, 0, 0, 0, 0, 0 );
});

}( this ));
</script>

+70/WROHUDWHV+70/OLNHORRVHQHVVVXFKDVQRQFORVHGHOHPHQWVDQGQRQHVFDSHG
DWWULEXWHYDOXHV7KLVPDNHVLWGLIÀFXOWWRFRPSDUHWZRGLIIHUHQW+70/VWULQJVWKDWDUHWUHDWHG
HTXDOO\E\DEURZVHU7KH+70/SOXJLQ https://github.com/JamesMGreene/qunit-
assert-html VROYHVWKHLVVXHLQWKHIROORZLQJZD\

(function( global ){
'use strict';

test('Test HTML strings are equal', function(assert) {

18
Instant Testing with QUnit
assert.htmlEqual('<B TITLE=test>test</B>', '<b
title="test">test</b>');
});

}( this ));

Writing a custom assertion


SOXJLQ $GYDQFHG
7KLVUHFLSHZLOOJXLGH\RXRQWKHEDVLVRIFUHDWLQJ48QLWSOXJLQV:HZLOOFUHDWHDFXVWRP
DVVHUWLRQPHWKRGDQGDGGLWWRWKHFRPPRQ48QLWDVVHUWLRQVHUYLFH

Getting ready
48QLWLVHDV\WRVWDUWZLWKDVLWSURYLGHVRQO\DIHZQHFHVVDU\DVVHUWLRQPHWKRGV
1HYHUWKHOHVV\RXFDQKDYHDVPDQ\PHWKRGVDV\RXZLVKE\XVLQJ48QLWSOXJLQV3OHQW\RI
SOXJLQVDUHDOUHDG\DYDLODEOHRQWKH,QWHUQHW%HVLGHV\RXPD\QHHG\RXURZQFXVWRPSOXJLQ
/XFNLO\48QLWKDVD&DOOEDFN$3,ZKLFKPDNHVLWHDV\<RXFDQVLPSO\UHJLVWHU\RXURZQ
KDQGOHURQWKHHQWU\DQGH[LWSRLQWVRIHYHU\WHVWLQJVFRSH WHVWVXLWHPRGXOHDQGWHVW RU
VXEVFULEHDKDQGOHUIXQFWLRQWRWKHHYHQWWKDWKDSSHQVHYHU\WLPHDVVHUWLRQSHUIRUPHGLQD
WHVWIRULQVWDQFH3KDQWRP-6 www.phantomjs.org LVZLGHO\XVHGWRUXQ48QLWWHVWVLQWKH
FRQVROH+RZHYHUWKH48QLWRXWSXWWHVWUHVXOWVDVDQ+70/UHSRUWLQ'20ZKLFKGRHVQ
WZRUN
ZLWKWKHFRPPDQGOLQH7KH3KDQWRP-6UXQQHU-DYD6FULSW 1RGH-6 XWLOLW\VROYHVWKLVLVVXH
E\WUDQVODWLQJWKH48QLWRXWSXWLQWRDIRUPDWFRPSDWLEOHZLWKWKHFRPPDQGOLQHLQWHUIDFH
,WUHJLVWHUVDFDOOEDFNZLWKWKHQUnit.logPHWKRGWRFROOHFWDQGVWRUHUHVXOWVRQHYHU\
DVVHUWLRQ,WDOVRVXEVFULEHVDKDQGOHURQWKHWHVWVXLWHFRPSOHWHHYHQWZLWKQUnit.done
ZKLFKRXWSXWVWKHDFFXPXODWHGUHVXOWVLQWKHUHTXLUHGZD\

+RZHYHULI\RXZULWHMXVWDQDVVHUWLRQPHWKRG\RXPD\GRZLWKRXWWKHFDOOEDFNV$3,LQPRVW
FDVHV<RXFDQVLPSO\DGG\RXUPHWKRGLQWKHQUnit.assertREMHFW

How to do it
 :ULWHDQDVVHUWLRQPHWKRG,PDJLQHWKDWZHQHHGDFRQYHQLHQWPHWKRGIRUDVVHUWLQJ
WKDWDVXSSOLHGHOHPHQWLVYLVLEOH7KHPHWKRGLVYHU\VLPSOH³LWRQO\FKHFNVLIWKH
HOHPHQWLVKLGGHQYLDVW\OHSURSHUWLHVYLVLELOLW\DQGGLVSOD\
function isVisible( node ) {
return node.style.visibility !== "hidden" &&
node.style.display !== "none";
}

19
Instant Testing with QUnit

 ([WHQGWKHQUnit.assertREMHFWZLWKWKLVQHZPHWKRG$VQUnit already provides


WKHextendKHOSHUZHGRQ
WQHHGWRXVHDWKLUGSDUW\OLEUDU\RUZULWHRXURZQKHOSHU
IRUH[WHQGLQJDQREMHFWWRGRLW
(function( global ){
"strict mode";
var QUnit = global.QUnit;
QUnit.extend( QUnit.assert, {
/**
* Check if a supplied element is visible
*
* @example assert.isVisible(document.
getElementById("#id"));
* @param {object} HTMLElement
*/
isVisible: function( node ) {
this.ok( node.style.visibility !== "hidden" &&
node.style.display !== "none" )
}
});
}( this ));

 6DYHWKLVFRGHLQWKHqunit-assert-is-visible.jsÀOH
 3URYLGHWKHWHVWUXQQHU+70/DVIROORZV
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Plugin Usage Example</title>
<link rel="stylesheet" href="http://code.jquery.com/qunit/
qunit-git.css" type="text/css" media="screen">
</head>
<body>
<h1 id="qunit-header">Plugin Usage Example</h1>
<h2 id="qunit-banner"></h2>
<div id="qunit-testrunner-toolbar"></div>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>
<div id="qunit-fixture">test markup</div>

<div id="sandbox">
<div id="myTarget"><!-- --></div>
</div>

20
Instant Testing with QUnit
<script type="text/javascript" src="http://code.jquery.com/
qunit/qunit-git.js"></script>
<script type="text/javascript" src="../js/qunit-assert-is-
visible.js"></script>
<script type="text/javascript">
"strict mode";
QUnit.test( "Test myTarget is visible", function( assert )
{
var node = document.getElementById("myTarget");
assert.isVisible( node );
});
</script>
</body>
</html>

 /RDGWKHWHVWUXQQHULQDEURZVHUDQGH[DPLQHWKHUHVXOWVVKRZQLQWKH
IROORZLQJVFUHHQVKRW

7HVWLQJH[FHSWLRQV 0HGLXP
,QWKLVUHFLSHZHZLOOOHDUQKRZWRKDQGOHWKHIXQFWLRQ
VH[FHSWLRQDOEHKDYLRUKRZWRGHÀQH
FXVWRPH[FHSWLRQVDQGKRZWRWHVWZKHWKHUH[FHSWLRQVDUHUHDOO\WKURZQIRULQYDOLGLQSXWDQG
WKHVHDUHH[FHSWLRQVRIWKHLQWHQGHGW\SHV

Getting ready
:H
YHOHDUQWDOUHDG\KRZWRWHVWZKHWKHURUQRWDIXQFWLRQSHUIRUPVWKHLQWHQGHGEXVLQHVV
ORJLF%XWZKDWDERXWLWVH[FHSWLRQDOEHKDYLRU"/HW
VVD\ZHVXSSO\LQYDOLGLQSXWSDUDPHWHUV
:HH[SHFWWKHIXQFWLRQWRZDUQXVDERXWWKHP7KHEHVWSUDFWLFHZRXOGEHWRFKHFNZKHWKHU
WKHSDUDPHWHUVKDYHEHHQSDVVHGRQWRWKHIXQFWLRQHQWU\SRLQWDQGWKURZQH[FHSWLRQVLIDQ\
RIWKHSDUDPHWHUVDUHLQYDOLG:HQHHGDZD\WRWHVWWKLVORJLFDVZHOO

21
Instant Testing with QUnit

'R\RXUHPHPEHUWKHutils.trimIXQFWLRQH[DPSOH":HKDYHWRPRGLI\LWQRZIRUFKHFNLQJ
WKHYDOLGLW\RIWKHLQSXWSDUDPHWHUV
var trim = function( str, charlist ) {

if ( typeof str !== "string" ) {


throw new this.InvalidTypeException("str argument must be a
string");
}
if ( charlist && typeof charlist !== "string" ) {
throw new this.InvalidTypeException("charlist argument must be a
string");
}
if ( !str.length ) {
throw new this.InvalidReferenceException("str argument must be
empty");
}

charlist = charlist || " \t\n\r\0\x0B";


return str.replace( new RegExp( "^[" + charlist + "]+|[" + charlist
+ "]+$", "g" ), '' );
}

:HOOWKHIXQFWLRQWKURZVH[FHSWLRQZKHQHYHULWGHWHFWVDQLQYDOLGLQSXWEXWWKHVHDUHFXVWRP
H[FHSWLRQVWKDWPXVWEHGHÀQHG7KLVFDQEHGRQHE\GHFODULQJQHZHUURUREMHFWFRQVWUXFWRUV
WKDWLQKHULWIURPVWDQGDUG-DYD6FULSWHUURUV3OHDVHÀQGWKHLPSOHPHQWDWLRQIRUWKLVLQWKH
IROORZLQJH[DPSOH
"strict mode";
var utils = (function( global ) {
"use strict";
return {
/**
* Port of PHP trim function
* @param {string} str
* @param {string} charlist
* @return {string}
*/
trim: function( str, charlist ) {
// function body from the example above
},
/**
* @constructor
*/
InvalidReferenceException: function( message ) {
this.name = "InvalidReferenceException";

22
Instant Testing with QUnit
this.message = message || "InvalidReferenceException thrown";
},
/**
* @constructor
*/
InvalidTypeException: function( message ) {
this.name = "InvalidTypeException";
this.message = message || "InvalidTypeException thrown";
}
};
}());

// Inherit from ReferenceError


utils.InvalidReferenceException.prototype = new ReferenceError();
utils.InvalidReferenceException.prototype.constructor = utils.
InvalidReferenceException;
// Inherit from TypeError
utils.InvalidTypeException.prototype = new TypeError();
utils.InvalidTypeException.prototype.constructor = utils.
InvalidTypeException;

How to do it
 'HÀQHWKHWHVWVFRSHDQGWKHDVVHUWH[SHFWHGH[FHSWLRQLVWKURZQ,QWKLVFDVH48QLW
SURYLGHVWKHthrowsPHWKRGZKLFKFDQEHGHVFULEHGZLWKWKHIROORZLQJFRGH
QUnit.test( "Test title", function( assert ) {
assert.throws( "callback throwing exception, expected
exception", "assertion title" );
});

 7HVWZKHWKHUutils.trimYDOLGDWHVWKHLQSXWSDUDPHWHUVDQGWKURZVWKH
LQWHQGHGH[FHSWLRQV
QUnit.test( "Test utils.trim contract violation", function( assert
){
assert.throws( function() {
utils.trim("");
}, utils.InvalidReferenceException,
"str agrument must not be empty"
);
assert.throws( function() {
utils.trim( 1 );
}, utils.InvalidTypeException,
"str agrument must be a string"
);

23
Instant Testing with QUnit
assert.throws( function() {
utils.trim( "string", 1 );
}, utils.InvalidTypeException,
"charlist agrument must be a string"
);
});

 /RDGWKHWHVWUXQQHULQDEURZVHUDQGH[DPLQHWKHUHVXOWVVKRZQLQWKH
IROORZLQJVFUHHQVKRW

7HVWLQJDV\QFKURQRXVFDOOV 0HGLXP
,QWKLVUHFLSHZHZLOOVHHKRZ48QLWGHDOVZLWKDV\QFKURQRXVFDOOEDFNV:HZLOOZULWHDQ
DVVHUWLRQWHVWIRUDQDV\QFKURQRXVO\FDOOHGIXQFWLRQ:HZLOODOVRJRWKURXJKDQH[DPSOHRI
WHVWLQJWKH;+5DVVRFLDWHGIXQFWLRQE\PRFNLQJWKHWUDQVIHUPHWKRG

Getting ready
2QHRIWKHJUHDWHVWIHDWXUHVRI-DYD6FULSWLVLWVDELOLW\WRFDOOIXQFWLRQVDV\QFKURQRXVO\
<RXFDQHDVLO\PDNHPXOWLSOHIXQFWLRQVWKDWH[HFXWHVLPXOWDQHRXVO\DVIROORZV
window.setTimeout( function() {
yourFunc1( param1, paramN );
}, 0);

24
Instant Testing with QUnit

$V\RXXQGHUVWDQGWKHVHIXQFWLRQVFDQQRWEHWHVWHGWKHZD\ZHGLGEHIRUH:HFDQQRWDVVHUW
RQWKHIXQFWLRQVWUDLJKWDIWHULWLVODXQFKHG2WKHUZLVHZHULVNDVVHUWLQJRQWKHFDOOEHIRUHLWLV
FRPSOHWHG:HZLOOHQFRXQWHUWKHVDPHSUREOHPZKLOHWHVWLQJKDQGOHUVVXEVFULEHGWRHYHQWV
DQGHYHQZKLOHXVLQJ-DYD6FULSWREMHFWVDVVRFLDWHGZLWKHYHQWV³IRULQVWDQFH;+5

How to do it
 'HÀQHWKHWHVWVFRSH)RUWHVWLQJDV\QFKURQRXVO\FDOOHGIXQFWLRQV48QLWSURYLGHV
WKHasyncTestPHWKRGZKLFKLQFRQWUDVWWRWKHWHVWPHWKRGUHFHLYHVDQDGGLWLRQDO
SDUDPHWHUZLWKDQXPEHURIH[SHFWHGDVVHUWLRQV7KXV48QLWZLOOVHWWKHWHVWDV
FRPSOHWHRQO\ZKHQWKHVXSSOLHGQXPEHURIDVVHUWLRQVLVSHUIRUPHG
/**
* Test async. called function
* @param {string} test title
* @param {number} number of expected assertions
* @param {function) test scope
*/
QUnit.asyncTest( "Test title", 1, function( assert ){
});

 $VVHUWZLWKLQWKHDV\QFKURQRXVFDOOEDFN6LQFHZHDVVHUWDV\QFKURQRXVO\ZHKDYH
WRFDOOWKHQUnit.startPHWKRGDVVRRQDVWKHODVWDVVHUWLRQLVGRQH7KLVZLOOOHW
48QLWNQRZZKHQLWFDQMXPSWRWKHQH[WWHVW
/**
* Test async. called function
* @param {string} test title
* @param {number} number of expected assertions
* @param {function) test scope
*/
QUnit.asyncTest( "Test async control", 1, function( assert ){
window.setTimeout( function() {
assert.ok( true, "captured" );
QUnit.start();
}, 0);
});

25
Instant Testing with QUnit

 /RDGWKHWHVWUXQQHULQDEURZVHUDQGH[DPLQHWKHUHVXOWVVKRZQLQWKH
IROORZLQJVFUHHQVKRW

There's more
7KHGHVFULEHGDSSURDFKZRUNVÀQHZLWKWKHWHVWIULHQGO\FRGH,PHDQWKHFRGHZKHUH
WKHDV\QFKURQRXVFDOODFFHSWVWHVWFDOOEDFNLQMHFWLRQRUÀUHVDQHYHQWRQZKLFKDKDQGOHU
LVVXEVFULEHG:KHQ\RXDUHZRUNLQJZLWKEODFNER[FRGH\RXKDYHQRZD\WRDVVHUWRQ
DV\QFKURQRXVFDOOVH[FHSWE\PRFNLQJWKHWUDQVIHUXWLOLW\ZKRVHLQWHUIDFH\RXDUHIDPLOLDU
ZLWKIRUH[DPSOHZKLOHXQLWWHVWLQJDIXQFWLRQWKDWUHOLHVRQ;+5ZHKDYHWRPRFNWKHKHOSHU
SHUIRUPLQJWKHUHTXHVW:HGRLWDQ\ZD\DVLWLQFOXGHVDQDFURVVQHWZRUNFRPPXQLFDWLRQDQG
JHWVRXWRIWKHXQLWVFRSH/HW
VVD\WKHIXQFWLRQXQGHUWHVWXVHVWKHM4XHU\JHWPHWKRG
:HNQRZLWVLQWHUIDFHDQGFDQRYHUULGHLWZLWKRXURZQPHWKRGZKLFKGRHVQRWKLQJEXW
UHSRUWWR48QLW<RXFDQVHHWKLVLQWKHIROORZLQJH[DPSOH
var
/**
* Object representing an abstract RSS Feed
* @constructor
*/
RssFeed = function() {
var xml;
return {
/**
* Load data from a remote source
* @param {string} url
*/

26
Instant Testing with QUnit
loadData: function( url ) {
$.get( url, function( rsp ){
xml = rsp;
});
},
/**
* Get last loaded data
*/
getData: function() {
return xml;
}
};
};

QUnit.asyncTest( "Test RssFeed.loadData requests remote source", 1,


function( assert ){
var feed = new RssFeed();

/**
* Mock XHR helper just for the case
* @param {string} url
* @param {function} callback
*/
$.get = function( url, callback ) {
window.setTimeout( function(){
callback("data");
assert.ok( true, "data requested" );
QUnit.start();
}, 0 );
};
feed.loadData("http://google.com");
});

7KHRSSFeedFRQVWUXFWRUSURGXFHVDQREMHFWWKDWFDQORDGGDWDIURPDUHPRWHVRXUFH'DWD
LVEHLQJORDGHGDV\QFKURQRXVO\XVLQJ$.get7RWHVWWKHPHWKRGZHRYHUULGH$.getZLWKRXU
RZQPHWKRG2XULPSOHPHQWDWLRQLQYRNHVDVXSSOLHGFDOOEDFNDVH[SHFWHGIURPjQuery.get
DQGGRHVLWDV\QFKURQRXVO\E\XVLQJsetTimeout7RZDUGVWKHHQGLWPDNHVWKHDVVHUWLRQ
DQGQRWLÀHV48QLWWKDWWKHWHVWLVFRPSOHWH

27
Instant Testing with QUnit

2UJDQL]LQJWHVWFDVHV 6LPSOH
,QWKLVUHFLSHZHZLOOOHDUQKRZWRNHHSWKHWHVWVORJLFDOO\RUJDQL]HGE\XVLQJWKHQUnit.
modulePHWKRG

Getting ready
:KLOHZRUNLQJRQDUHDOSURMHFWWKHQXPEHURIWHVWVFDQEHSUHWW\KXJHIRUH[DPSOH
WKHM4XHU\WHVWVXLWHFRQWDLQVDERXWWHVWVVKRZQLQWKHIROORZLQJVFUHHQVKRW

1DYLJDWLRQRQVXFKOLVWVZLWKRXWDQ\JURXSLQJDELOLWLHVZRXOGEHDWRXJKWDVN/XFNLO\48QLW
SURYLGHVWKHPRGXOHPHWKRGZKLFKDOORZVXVWRRUJDQL]HWKHWHVWVDQGUXQDVSHFLÀFJURXSRI
WHVWVLQGLYLGXDOO\

28
Instant Testing with QUnit

,PDJLQHZHKDYHDQDSSOLFDWLRQWKDWKDQGOHVXVHULQWHUDFWLRQZLWKWKH6LJQ,QDQG3DVVZRUG
5HVHWIRUPV:HGRQ
WQHHGDQ\LPSOHPHQWDWLRQGHWDLOVIRUQRZEXWUDWKHUDKLJKOHYHO
FRQFHSWDVIROORZV
var ResetPasswordForm = function() {
return {
/** Render and synchronize UI */
init: function() {
},
/** Show panel */
show: function() {},
/** Hide panel */
hide: function() {},
/** Validate form input */
validateInput: function() {}
};
},
SignInForm = function() {
return {
/** Render and synchronize UI */
init: function() {
},
/** Show panel */
show: function() {},
/** Hide panel */
hide: function() {},
/** Validate form input */
validateInput: function() {}
};
}

7KXVZHKDYHWZRREMHFWFRQVWUXFWRUVDQGWKUHHPHWKRGVHDFKWKDWPXVWEHWHVWHG/HW
VVSOLW
XSWKHWHVWLQWRWZRJURXSVRQHIRUWKH6LJQ,QIRUPDQGRQHIRU5HVW3DVVZRUG)RUP

How to do it
 'HÀQHDWHVWJURXS module IRUWKHSignInFormFRQVWUXFWRU
QUnit.module("SignInForm");

 3XWXQGHUQHDWKDOOWKHWHVWVUHOHYDQWWRWKHJURXS
QUnit.test( "show method", function( assert ){
// Stub assertion
assert.ok( true );
});

29
Instant Testing with QUnit
QUnit.test( "hide method", function( assert ){
// Stub assertion
assert.ok( true );
});
QUnit.test( "validateInput method", function( assert ){
// Stub assertion
assert.ok( true );
});
Repeat the flow for the ResetPasswordForm constructor.
// Define the group
QUnit.module("ResetPasswordForm");
QUnit.test( "show method", function( assert ){
// Stub assertion
assert.ok( true );
});
QUnit.test( "hide method", function( assert ){
// Stub assertion
assert.ok( true );
});
QUnit.test( "validateInput method", function( assert ){
// Stub assertion
assert.ok( true );
});

 /RDGWKHWHVWUXQQHULQDEURZVHUDQGH[DPLQHWKHUHVXOWVVKRZQLQWKH
IROORZLQJVFUHHQVKRW

30
Instant Testing with QUnit

8VLQJDVKDUHGVHWXS 0HGLXP
,QWKLVUHFLSHZHZLOOJXLGH\RXRQWHVWJURXSRSHUDWLRQVVXFKDVVKDUHGSUHOLPLQDU\WDVNVDQG
KRZWRNHHSWKHWHVWLQJHQYLURQPHQWFOHDQEHWZHHQWHVWH[HFXWLRQV

Getting ready
:KLOHZULWLQJPRUHDQGPRUHWHVWV\RXZLOOUHDOL]HWKDWWRJHWFOHDUUHVXOWV\RXQHHGWR
UHSHDWHGO\VHWXSWKHHQYLURQPHQWLQDVSHFLÀFVWDWHEHIRUHUXQQLQJDWHVWDQGUHVWRUHLWV
RULJLQDOVWDWHDIWHUZDUGV7KLVLVDWLPHFRQVXPLQJWDVNWKDWLQYROYHVFRGHGXSOLFDWLRQ
0RVWWHVWLQJIUDPHZRUNVLQFOXGLQJ48QLWVXSSRUWWKHVRFDOOHGVKDUHGVHWXSFRGH7KXV
\RXFDQGHÀQHWZRFDOOEDFNVIRUDJURXSRIWHVWV7KHÀUVWRQH setup WDNHVUHVSRQVLELOLW\
IRUUHFUHDWLQJWKHLQWHQGHGHQYLURQPHQWVWDWHSULRUWRHYHU\WHVWRIWKHH[HUFLVLQJJURXS
7KHVHFRQG teardown FOHDQVXSWKHFKDQJHVPDGHWRWKHHQYLURQPHQWDIWHUHYHU\WHVW
LVGRQH

7RFRQVLGHUWKHDGYDQWDJHVRIWKHDSSURDFKOHW
VWDNHDVDPSOHPRGXOHIRUWHVWLQJ
/**
* @constructor
* @returns {object}
*/
var SignInForm = function( boundingBox ) {
return {
/** Show panel */
show: function() {
boundingBox.style.display = "inline-block";
},
/** Hide panel */
hide: function() {
boundingBox.style.display = "none";
}
};
};

$FRQVWUXFWRUSURGXFLQJSignInFormFDQKLGHRUVKRZXS'XULQJLQLWLDOL]DWLRQWKHREMHFW
SignInForm ELQGVWRWKHHOHPHQWV$QLQVWDQFHRISignInForm H[SHFWVRI+70/OD\RXW
DQHOHPHQWWRELQGWR7KHSignInFormPHWKRGVDUHSHUPLWWHGWRPRGLI\'20ZLWKLQWKH
ERXQGLQJER[HOHPHQW7KHRUHWLFDOO\HYHU\VHFRQGWHVWVWDUWVRQDGLYHUVH'20WKDWPD\
DIIHFWWHVWUHVXOWV:HFDQSUHYHQWWKHSUREOHPE\DGGLQJDFOHDQERXQGLQJER[WRWKH'20
WUHHRQWKHWHVWVHWXSDQGUHPRYLQJLWZLWKWHDUGRZQ

31
Instant Testing with QUnit

How to do it
 'HÀQHDWHVWJURXS module IRUWKHSignInFormFRQVWUXFWRUDVIROORZV
QUnit.module("SignInForm");

 7KHVHFRQGSDUDPHWHUQUnit.moduleRIWKHPRGXOHPHWKRGH[SHFWVDQREMHFW
FDOOHGVKDUHGVHWXSFRQÀJXUDWLRQ
QUnit.module("SignInForm", {
setup: function() {
},
teardown: function() {
}
});

 $GGWKHFRGHDSSHQGLQJWKHERXQGLQJER[HOHPHQWWRWKH'20WUHHRQVHWXSDQGWKH
FRGHUHPRYLQJLWRQWHDUGRZQDVIROORZV
QUnit.module("SignInForm", {
// set up testing environment
setup: function() {
this.fixture = document.createElement("div");
document.getElementsByTagName('body')[ 0 ].appendChild(
this.fixture );
},
// restore the initial state
teardown: function() {
document.getElementsByTagName('body')[ 0 ].removeChild(
this.fixture );
}
});

 0RYHWKHSignInFormLQVWDQFHFUHDWLRQFRGHWRWKHVKDUHGVHWXSDVLWLVUHSHDWHGO\
UHTXLUHGE\HYHU\WHVWRIWKHJURXS
..
setup: function() {
this.fixture = document.createElement("div");
document.getElementsByTagName('body')[ 0 ].appendChild(
this.fixture );
this.form = new SignInForm( this.fixture );
},
..

 :ULWHWHVWVWRDVVHUWRQWKHSignInFormPHWKRGV
// Test hide method
QUnit.test( "hide method", function( assert ){

32
Instant Testing with QUnit
this.form.hide();
assert.strictEqual( this.fixture.style.display, "none" );
});
// Test show method
QUnit.test( "show method", function( assert ){
this.form.show();
assert.notStrictEqual( this.fixture.style.display, "none" );
});

 /RDGWKHWHVWUXQQHULQDEURZVHUDQGH[DPLQHWKHUHVXOWVVKRZQLQWKH
IROORZLQJVFUHHQVKRW

7HVWLQJXVHUDFWLRQV 0HGLXP
,QWKLVUHFLSHZHZLOOZULWHDVLPSOHFDOFXODWRUZLGJHW%\WHVWLQJZHZLOOVLPXODWHHQGXVHU
DFWLRQVDQGDVVHUWWKHLQWHQGHGDSSOLFDWLRQEHKDYLRU

Getting ready
%HIRUHMXPSLQJRQWKHWHVWZHGHÀQLWHO\QHHGDQDSSOLFDWLRQH[SHFWLQJXVHUDFWLRQV/HWLW
EHDVLPSOHFDOFXODWRUWKDWFDQQRWGRFRPSOH[RSHUDWLRQVEXWZLOOVXPXSWZRRIWKHVXSSOLHG
QXPEHUVDVVHHQLQWKHIROORZLQJVFUHHQVKRW

33
Instant Testing with QUnit

7RPDNHLWZRUNZHVXEVFULEHDKDQGOHUWRWKHclickHYHQWRQWKHCalculateEXWWRQ
7KHKDQGOHUWDNHVWKHYDOXHVRIWKHÀUVWDQGVHFRQGÀHOGVVXPVWKHPXSDQGSXWVWKH
UHVXOWLQWKHWKLUGÀHOG7KHLPSOHPHQWDWLRQRIWKHFRGHLVDVIROORZV
"strict mode";

this.utils = {
/**
* Fire a suplied event on a given element
* @param {object} el instance of HTMLElement
* @param {string} eventName
*/
trigger: function( el, eventName ) {
var e = document.createEvent("Event");
e.initEvent( eventName, true, true );
el.dispatchEvent( e );
},
/**
* Subscribe handler for event on a supplied element
* @param {object} el instance of HTMLElement
* @param {string} eventName
* @param {function} handlerCb
*/
subscribe: function( el, eventName, handlerCb ) {
el.addEventListener( eventName, function( e ) {
handlerCb( e );
}, false );
}
};

(function( global ){
var document = global.document,
utils = global.utils;

utils.subscribe( global, "load", function() {


utils.subscribe( document.getElementById("calc"), "click",
function( e ) {
var num1 = document.getElementById("num1"),
num2 = document.getElementById("num2"),
sum = document.getElementById("sum");

e.preventDefault();
sum.value = parseInt(num1.value, 10) +
parseInt(num2.value, 10);
});
});
}( this ));

34
Instant Testing with QUnit

/HW
VVDYHWKHVRXUFHFRGHLQWRWKHcalc.jsÀOHDQGORDGLWIURPWKH+70/OD\RXW
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Calc</title>
<link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/
css/bootstrap-combined.min.css" rel="stylesheet">
<style>
.container {
margin: 32px;
}
</style>
</head>
<body>
<div class="container">
<form class="form-inline">
<input id="num1" type="text" class="input-small"
placeholder="Summand">
<span>+</span>
<input id="num2" type="text" class="input-small"
placeholder="Summand">
<span>=</span>
<input id="sum" type="text" class="input-small"
placeholder="Sum">
<button id="calc" type="button" class="btn btn-
primary">Calculate</button>
<button type="reset" class="btn btn-danger">Reset</button>
</form>
</div>
<script src="./js/calc.js"></script>
</body>
</html>

1RZZHUXQWKLV+70/LQDEURZVHUDQGWHVWLWPDQXDOO\:LWKQXPEHUVW\SHGLQWKHEXWWRQ
FOLFNHGDQGWKHUHVXOWUHFHLYHGZHDUHQRZUHDG\WRZULWHDQDXWRPDWHGWHVWIRUWKHDFWLRQV
ZHMXVWSHUIRUPHG

35
Instant Testing with QUnit

How to do it
 6XEVFULEHDKDQGOHUWRWKH'20UHDG\HYHQWDQGJHWUHIHUHQFHVRQWKHLQYROYHG
HOHPHQWV7RPLPLFDXVHUW\SLQJQXPEHUVDQGFOLFNLQJWKHEXWWRQZHQHHGWRUHIHU
WRWKHLQSXWVDQGWKHEXWWRQ+RZHYHUHOHPHQWVZLOOEHDYDLODEOHRQO\ZKHQWKH
'20WUHHLVEXLOW6RZHREWDLQUHIHUHQFHVRQWKHHOHPHQWVLQWKHKDQGOHUZKLFKLV
FDOOHGRQWKH'20UHDG\HYHQW)RUWKDWZHXVHWKHKHOSHULQWURGXFHGLQWKHVDPSOH
DSSOLFDWLRQDVIROORZV
utils.subscribe( global, "load", function() {
var calc = document.getElementById("calc"),
num1 = document.getElementById("num1"),
num2 = document.getElementById("num2"),
sum = document.getElementById("sum");
});

 'HÀQHDWHVWVFRSHDQGVLPXODWHWKHXVHUEHKDYLRU:LWKLQWKHVFRSHZHDVVLJQWHVW
YDOXHVWRLQSXWVDQGWULJJHUWKHFOLFNHYHQW6LQFHWKHDSSOLFDWLRQUHVSRQGVWRWKH
FOLFNHYHQWLPPHGLDWHO\ZHFDQV\QFKURQRXVO\DVVHUWWKDWWKHYDOXHLQWKHUHVXOWÀHOG
HTXDOVWKHVXPRIQXPEHUVZHVXSSOLHG
utils.subscribe( global, "load", function() {
var calc = document.getElementById("calc"),
num1 = document.getElementById("num1"),
num2 = document.getElementById("num2"),
sum = document.getElementById("sum");

QUnit.test( "Test that calc sums up typed in numbers


by click", function( assert ){
num1.value = "5";
num2.value = "7";
utils.trigger( calc, "click" );
assert.strictEqual( sum.value, "12" );
});
});

36
Instant Testing with QUnit

 /RDGWKHWHVWUXQQHULQDEURZVHUDQGH[DPLQHWKHUHVXOWVVKRZQLQWKH
IROORZLQJVFUHHQVKRW

5XQQLQJ48QLWLQWKHFRQVROH $GYDQFHG
,QWKLVUHFLSHZHZLOOUXQ48QLWWHVWVIURPWKHFRPPDQGOLQHE\XVLQJWKH3KDQWRP-6
KHDGOHVVEURZVHU

Getting ready
:H
YHOHDUQWKRZWRUXQ48QLWLQDEURZVHUDQGLW
VVXLWDEOHLQWKHEHJLQQLQJ+RZHYHULQDQ
DGYDQFHGGHYHORSPHQWF\FOHWKHWHVWVDUHEHLQJH[HFXWHGE\DWRROVXFKDVDSLHFHRIEXLOG
DXWRPDWLRQVRIWZDUHRUDVCS version control system 6RZHQHHGWRUXQRXUWHVWVLQWKH
FRQVROH7KDWFDQEHGRQHZLWK48QLWWHVWVXVLQJDKHDGOHVVEURZVHU

/HW
VWDNHPhantomJS https://github.com/ariya/phantomjs )LUVWRIDOOZHKDYH
WRLQVWDOOWKHSDFNDJH7KDW
VTXLWHHDV\WRGRZLWKDSDFNDJHPDQDJHULQ0DF26RULQ8QL[
/LQX[)RUDQLQVWDQFHXVLQJ+RPHEUHZRQ0DF26\RXFDQGRLWOLNHWKLV
brew install phantomjs

37
Instant Testing with QUnit

2Q:LQGRZV\RXFDQVLPSO\GRZQORDGWKHH[HFXWDEOHÀOHIURPhttp://phantomjs.org/
download.htmlDQGDGGLWVORFDWLRQWRWKH:LQGRZVSDWKDVIROORZV

 Hold Win and press Pause


 &OLFNRQAdvanced System Settings
 &OLFNRQEnvironment Variables
 Append C:\phantomjsWRWKHPathYDULDEOH

:HZLOODOVRQHHGWKH3KDQWRP-65XQQHUVFULSW https://github.com/ariya/
phantomjs ZKLFKSOXJVLQWR48QLWWRWUDQVODWHWKHUHSRUWRXWSXWLQWR
FRPPDQGOLQHFRPSDWLEOHIRUPDW

How to do it
 ,QVWDOO3KDQWRP-6
 'RZQORDG3KDQWRP-65XQQHUDQGVDYHLWDVrunner.js
 5XQWKHWHVWVXLWHZLWKWKHIROORZLQJFRPPDQG
phantomjs runner.js test-suite.html

 ([DPLQHWKHUHVXOWVVKRZQLQWKHIROORZLQJVFUHHQVKRW

38
Instant Testing with QUnit

There's more
<RXPD\ÀQGSOHQW\RISURMHFWVRQ*LW+XE https://github.com XVLQJWKHTravis CI
https://travis-ci.org FRQWLQXRXVLQWHJUDWLRQVHUYHU7UDYLV&,UHSRUWVWRWKHSURMHFW
PHPEHUVZKHQVRPHERG\EUHDNVWKHEXLOG,WPHDQVLI\RXPDNHVRPHFKDQJHVLQWKH
SURMHFWFRGHDQGXVHWKHpushFRPPDQGWRFRPPLWWR*LW+XE7UDYLV&,FDQDXWRPDWLFDOO\
FKHFNLIWKHFKDQJHGFRGHVWLOODGKHUHVWRWKHVW\OHJXLGHDQGOLQWHUUHTXLUHPHQWVLIWKHXQLW
WHVWVWLOOSDVVHVDQGVRRQ+RZGRHVLWZRUN":LWKHYHU\FRGHFRPPLW7UDYLV&,SXOOVGRZQ
WKHSURMHFW
VZRUNLQJGLUHFWRU\IURP*LW+XEDQGUXQVWKHGruntWDVNUXQQHU www.gruntjs.
com *UXQWSHUIRUPVWKHWDVNVVXSSOLHGLQWKHFRQÀJXUDWLRQÀOH Gruntfile.js $PRQJ
RWKHUWDVNV\RXFDQDVVLJQ48QLWWHVWV7UDYLV&,VWRUHVWKHWHVWUHVXOWVLQWKHEXLOGUHSRUWDQG
QRWLÀHVWKHFRPPLWLIWKHEXLOGIDLOV

/HW
VVHHKRZZHFDQWDNHDGYDQWDJHRIWKLVDSSURDFK

 ,QVWDOO1RGHMV)RU0DF26;ZHFDQDJDLQUHFRXUVHWR+RPHEUHZ
brew install node

,QVWDOODWLRQLQVWUXFWLRQVIRURWKHUSODWIRUPVFDQEHIRXQGRQWKLVSDJH
https://github.com/joyent/node/wiki/Installing-Node.js-via-
package-manager
 ,QVWDOO*UXQW,WFDQEHHDVLO\GRQHQRZWKDWZHKDYHWKHNode.jsSDFNDJHPDQDJHU
npm install -g grunt-cli

 0RYHWRWKHSURMHFWZRUNLQJGLUHFWRU\
 &RQÀJXUHWKHSURMHFWZLWKpackage.json
{
"name": "project-name",
"description": "Project description",
"version": "0.0.1",
"devDependencies": {
"grunt": "~0.4.1",
"grunt-contrib-qunit": "~0.2.1",
"qunitjs": "1.x"
}
}

39
Instant Testing with QUnit

 ,QVWDOOWKHUHTXLUHGGHSHQGHQFLHV
npm install

 6HWXSWKHGruntWDVNWRÀUHXSWKH48QLWWHVWUXQQHULQtest-suite.html
module.exports = function(grunt) {
grunt.loadNpmTasks("grunt-contrib-qunit");

grunt.initConfig({
qunit: {
all: ["tests/test-suite.html"]
}
});

grunt.registerTask("test", ["qunit"]);
grunt.registerTask("default", ["test"]);

};

 ([DPLQHWKHUHVXOWVVKRZQLQWKHIROORZLQJVFUHHQVKRW

Cross-browser-distributed
WHVWLQJ $GYDQFHG
7KLVUHFLSHZLOOJXLGH\RXRQDXWRPDWLQJFOLHQWVLGHFURVVSODWIRUPEURZVHUWHVWLQJXVLQJWKH
command-line tool Bunyip

40
Instant Testing with QUnit

Getting ready
,QFRQWUDVWWRWKHVHUYHUVLGHVRIWZDUH-DYD6FULSWDSSOLFDWLRQVDUHEHLQJH[HFXWHGRQWKHFOLHQW
VLGHDQGWKHUHIRUHGHSHQGRQWKHXVHUEURZVHU1RUPDOO\SURMHFWVSHFLÀFDWLRQLQFOXGHVWKHOLVW
RIWKHEURZVHUVDQGSODWIRUPVWKDWWKHDSSOLFDWLRQPXVWVXSSRUW7KHORQJHUWKHOLVWWKHKDUGHU
LVFURVVEURZVHUFRPSDWLELOLW\WHVWLQJ)RUH[DPSOHM4XHU\VXSSRUWVEURZVHUVRQGLIIHUHQW
SODWIRUPV7KHSURMHFWLVIXOO\WHVWHGLQHYHU\GHFODUHGHQYLURQPHQWZLWKHYHU\VLQJOHFRPPLW
7KDWLVSRVVLEOHWKDQNVWRWKHGLVWULEXWHGWHVWLQJWRRO7HVW6ZDUP swarm.jquery.org <RX
PD\DOVRKHDURIRWKHUWRROVVXFKDV-V7HVW'ULYHU code.google.com/p/js-test-driver 
RU.DUPD karma-runner.github.io :HZLOOWDNH%XQ\LS https://github.com/
ryanseddon/bunyip DVLWKDVVZLIWO\EHHQJDLQLQJSRSXODULW\UHFHQWO\

+RZGRHVLWZRUN"<RXODXQFKWKHWRROIRUDWHVWUXQQHU+70/DQGLWSURYLGHVWKHFRQQHFW
HQGSRLQW IP:port DQGODXQFKHVDORFDOO\LQVWDOOHGEURZVHULIFRQÀJXUHG$VVRRQDV
\RXÀUHXSWKHDGGUHVVLQDEURZVHUWKHFOLHQWLVFDSWXUHGE\%XQ\LSDQGWKHFRQQHFWLRQ
LVHVWDEOLVKHG:LWK\RXUFRQÀUPDWLRQ%XQ\LSUXQVWKHWHVWVLQHYHU\FRQQHFWHGEURZVHU
WRFROOHFWDQGUHSRUWUHVXOWV6HHWKHIROORZLQJÀJXUH

%XQ\LSLVEXLOWRQWRSRIWKH<HWLWRRO www.yeti.cx WKDWZRUNVZLWKYUI Test48QLWMocha


JasmineRU'2+%XQ\LSFDQEHXVHGLQFRQMXQFWLRQZLWK%URZVHU6WDFN6RZLWKDSDLG
DFFRXQWDW%URZVHU6WDFN www.browserstack.com \RXFDQPDNH%XQ\LSUXQ\RXUWHVWV
RQKXQGUHGVRIUHPRWHO\KRVWHGEURZVHUV

41
Instant Testing with QUnit

7RLQVWDOOWKHWRROW\SHLQWKHFRQVROHDVIROORZV
npm install -g bunyip

+HUHZHUHFRXUVHWRWKHNode.jsSDFNDJHPDQDJHUWKDWLVSDUWRINode.js6RLI\RXGRQ
W
KDYHNode.jsLQVWDOOHGÀQGWKHLQVWDOODWLRQLQVWUXFWLRQVRQWKHIROORZLQJSDJH

https://github.com/joyent/node/wiki/Installing-Node.js-via-package-
manager

1RZZHDUHUHDG\WRVWDUWXVLQJ%XQ\LS

How to do it
 $GGWRWKH48QLWWHVWVXLWH test-suite.html WKHIROORZLQJFRQÀJXUDWLRQRSWLRQ
WRSUHYHQWLWIURPDXWRVWDUWLQJEHIRUHWKHSOXJLQFDOOEDFNLVVHWXS
if (QUnit && QUnit.config) {
QUnit.config.autostart = false;
}

 /DXQFKD<HWLKXERQSRUW GHIDXOWFRQÀJXUDWLRQ DQGXVHtest-suite.html


bunyip -f test-suite.html

 &RS\WKHFRQQHFWRUDGGUHVV IRUH[DPSOHhttp://127.0.0.1:9000 


IURPWKHRXWSXWDQGÀUHLWXSLQGLYHUVHEURZVHUV<RXFDQXVH2UDFOH9LUWXDO%R[
www.virtualbox.org WRODXQFKEURZVHUVLQYLUWXDOPDFKLQHVVHWXSRQHYHU\
SODWIRUP\RXQHHG

42
Instant Testing with QUnit

 ([DPLQHWKHUHVXOWVVKRZQLQWKHIROORZLQJVFUHHQVKRW

%XLOGLQJDZHESURMHFW $GYDQFHG
:KLOHZRUNLQJZLWKD-DYD6FULSWSURMHFWEXLOGPD\VRXQGRGGLW
VTXLWHUHOHYDQWKHUH8VLQJ
EXLOGDXWRPDWLRQVRIWZDUHZHFDQSHUIRUPDEXQFKRISURMHFWUHODWHGWDVNVZLWKDVLQJOH
FRPPDQG,W
VFRPPRQSUDFWLFHQRZDGD\VWRUXQWDVNVVXFKDV-DYD6FULSWOLQWLQJFRGH
VWDQGDUGYDOLGDWLRQ&66SUHSURFHVVLQJDQG$3,GRFXPHQWDWLRQXSGDWHZLWKDEXLOGVFULSW
PDQXDOO\RUXVLQJFRQWLQXRXVLQWHJUDWLRQWRROV7KLVUHFLSHVKRZVKRZWKH48QLWWHVWLQJWDVN
FDQEHDGGHGWRWKHSURMHFWEXLOGVFULSW

43
Instant Testing with QUnit

Getting ready
)RUWKLVWDVNZHZLOOXVHApache Ant (http://ant.apache.org/)IRUEXLOGDXWRPDWLQJ
2Q0DF26;$QWLVDYDLODEOHE\GHIDXOW2Q8QL[/LQX[LWLVDYDLODEOHLQSRUWVDQG\RXFDQXVH
DSDFNDJHPDQDJHUWRLQVWDOO$QW$VIRU:LQGRZV\RXFDQÀQGWKHLQVWDOODWLRQLQVWUXFWLRQVLQ
WKHRIÀFLDOPDQXDODWant.apache.org/manual/index.html

6LQFH$QWUXQV48QLWWHVWVRQWKHFRPPDQGOLQHZHZLOOXVH3KDQWRP-6DVLWLVGLVFXVVHG
LQWKHSUHFHGLQJVHFWLRQ+RZHYHULIZHDUHWRVDWLVI\WKLUGSDUW\VRIWZDUHH[HFXWLQJWKHEXLOG
VFULSWVXFKDVFRQWLQXRXVLQWHJUDWLRQWRROVZHQHHGWRPDNHWKH48QLWUHSRUWLQDVSHFLÀF
IRUPDW0RVWWRROVDFFHSWJUnit XML:HFDQWUDQVODWHWKH48QLWRXWSXWWR-8QLWIRUPDW
ZLWKDSOXJLQFDOOHGJUnit Logger https://github.com/jquery/qunit-reporter-
junit 6RZHDGGWRWKHWHVWUXQQHU+70/WKHSOXJLQ-DYD6FLSWPRGXOHDQGWKHIROORZLQJ
FRQÀJXUDWLRQRSWLRQ

QUnit.jUnitReport = function(report) {
console.log(report.xml);
};

%XWKHUHSRSVXSDQRWKHUSUREOHP7KH3KDQWRP-65XQQHUVFULSWQRZEUHDNV-8QLW;0/
ZLWKLWVRZQUHSRUWYHUVLRQ<RXFDQVLPSO\FRPPHQWRXWconsole.logRFFXUUHQFHVLQWKH
QUnit.done and QUnit.testDoneFDOOEDFNIXQFWLRQVDWWKHHQGRIrunner.js and save
LWVD\DVrunner-muted.js

How to do it
 6HWXSWKHEXLOGVFULSWDVbuild.xmlLQWKHURRWRIWKHSURMHFWZRUNLQJGLUHFWRU\
<?xml version="1.0"?>
<!DOCTYPE project>
<project name="tree" basedir="." default="build">
<target name="build" description="runs QUnit tests using
PhantomJS">
<!-- Clean up output directory -->
<delete dir="./build/qunit/"/>
<mkdir dir="./build/qunit/"/>
<!-- QUnit Javascript Unit Tests -->
<echo message="Executing QUnit Javascript Unit Tests..."/>

</target>

44
Instant Testing with QUnit
</project>

 ([WHQGLWZLWKWKHEXLOGWDUJHWWRUXQ48QLWWHVWV
<?xml version="1.0"?>
<!DOCTYPE project>
<project name="tree" basedir="." default="build">
<target name="build" description="runs QUnit tests using
PhantomJS">
<!-- Clean up output directory -->
<delete dir="./build/qunit/"/>
<mkdir dir="./build/qunit/"/>
<!-- QUnit Javascript Unit Tests -->
<echo message="Executing QUnit Javascript Unit Tests..."/>
<exec executable="/usr/local/bin/phantomjs" output="./build/
qunit/qunit-results.xml">
<arg value="./vendors/Runner/runner-muted.js" />
<arg value="http://test.dev/qunit/tests/test11/index.html" />
</exec>
</target>
</project>

 5XQ$QWLQWKHVDPHGLUHFWRU\ZKHUHbuild.xmlLVORFDWHG
ant

 ([DPLQHWKHUHVXOWVVKRZQLQWKHIROORZLQJVFUHHQVKRW

45
Instant Testing with QUnit

QUnit and CI – setting up


-HQNLQV $GYDQFHG
&RQWLQXRXVLQWHJUDWLRQLVWKHSUDFWLFHRIDXWRPDWLQJSUHGHSOR\PHQWUHODWHGWDVNVWKDWDOORZV
WHDPPHPEHUVWRLQWHJUDWHWKHLUZRUNLQWRWKHSURMHFWIUHTXHQWO\$VDQDSSURDFK&,GRHVQ
W
UHTXLUHDQ\SDUWLFXODUWRROEXWLVPRVWSURÀWDEOHZKHQXVHGZLWKD&RQWLQXRXV,QWHJUDWLRQ
6HUYHU<RXFDQÀQGPDQ\RSHQVRXUFHVHUYHUVDYDLODEOHQRZDGD\V<HW-HQNLQV NQRZQDV
+XGVRQWLOOwww.jenkins-ci.org VHHPVWREHWKHPRVWSRSXODUVROXWLRQ,WEHFDPH
NQRZQDVWKHEHWWHUDOWHUQDWLYHWRCruiseControl WKHÀUVWRULJLQDOLPSOHPHQWDWLRQRIWKH&,
DXWRPDWLRQFRQFHSW DQGZRQPDQ\RSHQVRXUFHDZDUGV-HQNLQVLVTXLWHHDV\WRVHWXSDQG
KDVDKXJHUHSRVLWRU\RISOXJLQVWKDWLQWHJUDWHWKHVHUYHUZLWKDZLGHUDQJHRIWKLUGSDUW\WRROV

:HZLOOQRZLQVWDOOD-HQNLQV&,VHUYHUDQGVHWLWXSIRUD48QLWWHVWUXQQHUMRE

Getting ready
7KHEHVWZD\WRH[SODLQ&,LVWRVKRZLWLQDQH[DPSOH/HW
VDVVXPH\RXDUHRQHRIWKH
GHYHORSHUVZRUNLQJRQDSURMHFW<RX
UHXVLQJDYHUVLRQFRQWUROV\VWHPVR\RXUFROODERUDWLRQ
RQWKHFRGHJRHVÀQH<HWZKHQHYHUDVWDEOHUHOHDVHLVWDJJHG\RXLQHYLWDEO\UXQLQWR
SUREOHPV LQWHJUDWLRQKHOO  <RXDWWHPSWWREXLOGWKHUHOHDVHDQGJHWVXUSULVHGZLWKWKH
UHVXOWV:KLOHZRUNLQJZLWKWKHIURQWHQGEXLOGDVDWHUPPD\VRXQGRGGEXWLWLV
QRQHWKHOHVVTXLWHUHOHYDQWKHUH'XULQJUHOHDVHDQXPEHURIWDVNVPXVWEHSHUIRUPHG
FRGHTXDOLW\DQDO\VLVFRGHVWDQGDUGYLRODWLRQFKHFNVWHVWV&66SUHSURFHVVLQJ&RIIH6FULSW
RU7\SH6FULSWFRPSLODWLRQ$3,GRFXPHQWDWLRQJHQHUDWLRQLPDJHRSWLPL]DWLRQ-6&66
FRQFDWHQDWLRQDQGFRPSUHVVLRQ VHHWKHDUWLFOHH5bp Ant Build Script at https://github.
com/h5bp/ant-build-script ,WJHWVHYHQEHWWHURQWKHVHUYHUVLGHVRXUFHV
SURMHFWVL]HLVPHDVXUHGVRIWZDUHPHWULFVDUHFDOFXODWHGPHVVGHWHFWLRQLVSHUIRUPHG
DQGFRGHGXSOLFDWHVDUHLGHQWLÀHG VHHTemplate for Jenkins Jobs for PHP Projects at
http://jenkins-php.org/index.html 7KDWLVZKDWZHFDOODEXLOG:HDXWRPDWHWKH
EXLOGSURFHVVLQJXVLQJVSHFLDOVRIWZDUHVXFKDV$SDFKH$QW0DYHQ*UXQW3KLQJDQG%DVK

&,VHUYHUOLVWHQVWRWKHXSGDWHVRQWKHYHUVLRQFRQWUROUHSRVLWRU\:KHQHYHU\RXFRPPLW
WKHVHUYHUSXOOVWKHZRUNLQJYHUVLRQRIWKHSURMHFWDQGUXQVWKHbuild script,WFROOHFWVWKH
UHSRUWVRIWKHWRROVSHUIRUPLQJWKHEXLOGWDVNVDQGOHWV\RXNQRZE\HPDLOZKHWKHUWKHEXLOG
ZDVVXFFHVVIXORUQRW7KXV\RXNQRZLPPHGLDWHO\LIWKHFKDQJHV\RXPDGHFDXVHDQ\
WURXEOHGXULQJLQWHJUDWLRQ

7RJHWDEHWWHUJULSRQ&,ZHLQVWDOO-HQNLQVVHUYHUDQGGHÀQHDMREUXQQLQJ48QLWWHVWV

46
Instant Testing with QUnit

How to do it
 ,QVWDOO-HQNLQV,I\RXZRUNXQGHU0DF26;MXVWUXQ
sudo brew install jenkins

7RLQVWDOO-HQNLQVRQ8QL[/LQX[SOHDVHÀQGLQVWUXFWLRQVDWhttps://wiki.
jenkins-ci.org/display/JENKINS/Use+JenkinsDQGIRU:LQGRZVDW
https://wiki.jenkins-ci.org/display/JENKINS/Installing+Jenkins+
as+a+Windows+service

 Create a dedicated user JenkinsIRUWKHVHUYHUGDHPRQ


 Fire up http://localhost:8080 E\GHIDXOWFRQÀJXUDWLRQ LQ\RXUEURZVHU
 ([DPLQHWKH-HQNLQVGDVKERDUGSDJHVKRZQLQWKHIROORZLQJVFUHHQVKRW

 (QDEOHWKH*LWSOXJLQ
 1DYLJDWHWRJenkins | Manage Jenkins | Manage Plugins | Available and
select Git plugin
 &RQILUP\RXUFKRLFHE\FOLFNLQJRQWKHOKEXWWRQDWWKHERWWRPRIWKHSDJH

47
Instant Testing with QUnit

 $GGDQHZMREWR-HQNLQV
 (QWHUWKHJenkins DashboardSDJHDQGFOLFNRQWKHAdd new jobOLQN
 6SHFLI\WKHSURMHFW job QDPHLQWKHGLVSOD\HGIRUP
 &KRRVHBuild a free-style software projectDVVKRZQLQWKH
IROORZLQJVFUHHQVKRW

48
Instant Testing with QUnit

 ([DPLQHWKHQH[WVFUHHQDVVKRZQLQWKHIROORZLQJVFUHHQVKRW

49
Instant Testing with QUnit

 &KRRVHWKHSource Code ManagementGit section and type in your Git


UHPRWHUHSRVLWRU\ IRUH[DPSOHgit@dsheiko.local:repo/tree
 Select Publish JUnit test result reportLQWKHPost-build ActionsFRPERER[
DQGVSHFLI\WKHORFDWLRQRIRXUEXLOGJHQHUDWHGUHSRUWILOH build/qunit/
qunit-results.xml
 &RQILUPFKDQJHVE\FOLFNLQJRQWKHSaveEXWWRQ

 &RPHEDFNWRWKHJenkins DashboardSDJHDQGFOLFNRQWKHBuild NowOLQN


 )LQGDQHZUHFRUGDGGHGLQWKHBuild HistoryEORFNRIWKH-HQNLQVVLGHEDUPHQX
 2SHQWKHOLQNDQGH[DPLQHWKHbuild detailsSDJH
 )ROORZWKHTest ResultOLQNDWWKHERWWRPWRJHWUHSRUWGHWDLOV
 ([DPLQHWKHUHVWRIWKHUHVXOWVVKRZQLQWKHIROORZLQJVFUHHQVKRW

 &RQÀJXUHWKHMREIRUFRQWLQXRXVLQWHJUDWLRQ
 2SHQWKHSURMHFWSDJHDQGFOLFNRQConfigure

50
Instant Testing with QUnit

 &KRRVHWKHEXLOGUHTXLUHGQRWLILFDWLRQPHWKRGLQBuild Triggers and specify


DUXQVFKHGXOHLQWKHScheduleILHOGDVVKRZQLQWKHIROORZLQJVFUHHQVKRW

 3LFNXSE-mail notificationIURPWKHPost-build ActionsFRPERER[DQGW\SH


LQ\RXUHPDLODGGUHVV

 &RPPLWDFKDQJHLQ\RXUSURMHFWDQGSXVKLWWRWKH*LWVHUYHUDQGJHWDXWRPDWLFDOO\
QRWLÀHGLIWKH48QLWWHVWVIDLO

51
Instant Testing with QUnit

There's more
,I\RXDUHDQDXWKRUL]HGjenkinsXVHUDWWKH*LWVHUYHU\RXZRQ
WEHDEOHWRPDNHDEXLOG
XVLQJWKH*LWSOXJLQ<RXQHHGDSDLURI66+NH\VWRH[FKDQJHEHWZHHQWKHgit and jenkins
XVHUVDVGHVFULEHGLQWKHGit DocumentationVHFWLRQ http://git-scm.com/book/ch4-
4.html ,I\RXDUHRQD0DFWKHDUWLFOHSetting up a Git Server on Mac OSX http://blog.
smitec.net/posts/setting-up-a-git-server-on-osx/ ZLOOEHRIJUHDWKHOS

,QRUGHUWRXVHWKH-HQNLQV*LWSOXJLQXQGHU8QL[/LQX[ZHORJLQDVjenkins and create a


SDLURI56$NH\V
cd ~/.ssh
ssh-keygen -f jenkins_rsa -C 'Some comment' -N '' -t rsa -q

7KHQZHFRS\GHULYHGWKHjenkins_rsa.pubÀOHWRWKH*LWVHUYHU
scp jenkins_rsa.pub git@our-git-server.com:/home/git/.ssh

:HDOVRKDYHWRHGLW~/.ssh/configWRFRQÀJXUHWKH66+FOLHQW
Host git@our-git-server.com
IdentityFile ~/.ssh/jenkins_rsa

1RZ-HQNLQVLVVXSSRVHGWREHDXWRPDWLFDOO\DXWKRUL]HGRQWKH*LWVHUYHU

52
Thank you for buying
Instant Testing with QUnit

$ERXW3DFNW3XEOLVKLQJ
3DFNWSURQRXQFHG
SDFNHG
SXEOLVKHGLWVÀUVWERRNMastering phpMyAdmin for Effective MySQL
ManagementLQ$SULODQGVXEVHTXHQWO\FRQWLQXHGWRVSHFLDOL]HLQSXEOLVKLQJKLJKO\IRFXVHG
ERRNVRQVSHFLÀFWHFKQRORJLHVDQGVROXWLRQV

2XUERRNVDQGSXEOLFDWLRQVVKDUHWKHH[SHULHQFHVRI\RXUIHOORZ,7SURIHVVLRQDOVLQDGDSWLQJDQG
FXVWRPL]LQJWRGD\
VV\VWHPVDSSOLFDWLRQVDQGIUDPHZRUNV2XUVROXWLRQEDVHGERRNVJLYH\RXWKH
NQRZOHGJHDQGSRZHUWRFXVWRPL]HWKHVRIWZDUHDQGWHFKQRORJLHV\RX
UHXVLQJWRJHWWKHMREGRQH
3DFNWERRNVDUHPRUHVSHFLÀFDQGOHVVJHQHUDOWKDQWKH,7ERRNV\RXKDYHVHHQLQWKHSDVW2XU
XQLTXHEXVLQHVVPRGHODOORZVXVWREULQJ\RXPRUHIRFXVHGLQIRUPDWLRQJLYLQJ\RXPRUHRIZKDW
\RXQHHGWRNQRZDQGOHVVRIZKDW\RXGRQ
W

3DFNWLVDPRGHUQ\HWXQLTXHSXEOLVKLQJFRPSDQ\ZKLFKIRFXVHVRQSURGXFLQJTXDOLW\
FXWWLQJHGJHERRNVIRUFRPPXQLWLHVRIGHYHORSHUVDGPLQLVWUDWRUVDQGQHZELHVDOLNH
)RUPRUHLQIRUPDWLRQSOHDVHYLVLWRXUZHEVLWHwww.packtpub.com

:ULWLQJIRU3DFNW
:HZHOFRPHDOOLQTXLULHVIURPSHRSOHZKRDUHLQWHUHVWHGLQDXWKRULQJ%RRNSURSRVDOVVKRXOGEH
sent to author@packtpub.com,I\RXUERRNLGHDLVVWLOODWDQHDUO\VWDJHDQG\RXZRXOGOLNHWR
GLVFXVVLWÀUVWEHIRUHZULWLQJDIRUPDOERRNSURSRVDOFRQWDFWXVRQHRIRXUFRPPLVVLRQLQJHGLWRUV
ZLOOJHWLQWRXFKZLWK\RX
:H
UHQRWMXVWORRNLQJIRUSXEOLVKHGDXWKRUVLI\RXKDYHVWURQJWHFKQLFDOVNLOOVEXWQRZULWLQJ
H[SHULHQFHRXUH[SHULHQFHGHGLWRUVFDQKHOS\RXGHYHORSDZULWLQJFDUHHURUVLPSO\JHWVRPH
DGGLWLRQDOUHZDUGIRU\RXUH[SHUWLVH
JavaScript Unit Testing
,6%13DSHUEDFNSDJHV

<RXUFRPSUHKHQVLYHDQGSUDFWLFDOJXLGHWRHIIHFLHQWO\
SHUIRUPLQJDQGDXWRPDWLQJ-DYD6FULSWXQLWWHVWLQJ
 /HDUQDQGXQGHUVWDQGXVLQJSUDFWLFDO
H[DPSOHVV\QFKURQRXVDQGDV\QFKURQRXV
-DYD6FULSWXQLWWHVWLQJ

 &RYHUWKHPRVWSRSXODU-DYD6FULSW8QLW7HVWLQJ
)UDPHZRUNVLQFOXGLQJ-DVPLQH<8,7HVW48QLW
DQG-V7HVW'ULYHU

 $XWRPDWHDQGLQWHJUDWH\RXU-DYD6FULSW8QLW
7HVWLQJIRUHDVHDQGHIÀFLHQF\

M4XHU\8,7KH8VHU
,QWHUIDFH/LEUDU\IRUM4XHU\
,6%13DSHUEDFNSDJHV

%XLOGKLJKO\LQWHUDFWLYHZHEDSSOLFDWLRQV
ZLWKUHDG\WRXVHZLGJHWVIURPWKHM4XHU\8VHU
,QWHUIDFH/LEUDU\
 3DFNHGZLWKH[DPSOHVDQGFOHDUH[SODQDWLRQV
RIKRZWRHDVLO\GHVLJQHOHJDQWDQGSRZHUIXO
IURQWHQGLQWHUIDFHVIRU\RXUZHEDSSOLFDWLRQV

 $VHFWLRQFRYHULQJWKHZLGJHWIDFWRU\LQFOXGLQJDQ
LQGHSWKH[DPSOHRQKRZWREXLOGDFXVWRPM4XHU\
8,ZLGJHW

 8SGDWHGFRGHZLWKVLJQLÀFDQWFKDQJHVDQGÀ[HV
WRWKHSUHYLRXVHGLWLRQ

Please check www.PacktPub.com for information on our titles

Potrebbero piacerti anche