In this post we will examine the 2nd of 4 JavaScript Unit Testing frameworks... JSSpec.
Previous parts of this series...
In subsequent posts we will cover QUnit, and YUI Test.
I like this Unit Testing framework a lot more than JsUnit for the following reasons...
- As you can see from the screenshot below, it has a nice interactive Test Runner
- The testing API has an easy to read Fluent Interface (a.k.a. It reads like English)
- The project appears to still be active... the latest version was available on Sep 23, 2008
- The test files don't need to be hosted in a web server in order to run (unlike JsUnit).
Ok, ok, enough talk. Lets get down to using this framework...
I added functionality to the nasty Pig Latin JavaScript to not only translate words, but also sentences.*
*Note: Yet again, this code is not optimal and it will be refactored in a future post.
function EnglishToPigLatin() { this.CONSONANTS = 'bcdfghjklmnpqrstvwxyz'; this.VOWELS = 'aeiou'; this.Translate = function(english, splitType) { var translated = ''; console.log('English: ', english); var words = english.split(/\s+/); console.log('Split Words: ', words); for (var i = 0; i < words.length; ++i) { console.log('Word ', i, ': ', words[i]); translated += this.TranslateWord(words[i]); if (i+1 < words.length) translated += ' '; } console.log('Translated: ', translated); console.log('----------'); return translated; } this.TranslateWord = function(english) { /*const*/ var SYLLABLE = 'ay'; var pigLatin = ''; if (english != null && english.length > 0 && (this.VOWELS.indexOf(english[0].toLowerCase()) > -1 || this.CONSONANTS.indexOf(english[0].toLowerCase()) > -1 )) { if (this.VOWELS.indexOf(english[0].toLowerCase()) > -1) { pigLatin = english + SYLLABLE; } else { var preConsonants = ''; for (var i = 0; i < english.length; ++i) { if (this.CONSONANTS.indexOf(english[i].toLowerCase()) > -1) { preConsonants += english[i]; if (preConsonants.toLowerCase() == 'q' && i+1 < english.length && english[i+1].toLowerCase() == 'u') { preConsonants += 'u'; i += 2; break; } } else { break; } } pigLatin = english.substring(i) + preConsonants + SYLLABLE; } } return pigLatin; } }
Like I said before, JSSpec provides a nicer Fluent Interface that makes the tests read more like English. The follow are several sets of Unit Tests to exercise the existing functionality, plus the new feature I added since the last post.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ko"> <head> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" /> <title>JSSpec results</title> <link rel="stylesheet" type="text/css" href="JSSpec.css" /> <script type="text/javascript" src="diff_match_patch.js"></script> <script type="text/javascript" src="JSSpec.js"></script> <script type="text/javascript" src="PigLatinBad.js"></script> <script type="text/javascript">// <![CDATA[ describe('Invalid Arguments', { before_each : function() { englishToPigLatin = new EnglishToPigLatin(); }, 'Passing Null Should Return Blank': function() { value_of(englishToPigLatin.TranslateWord(null)).should_be(''); }, 'Passing Blank Should Return Blank': function() { value_of(englishToPigLatin.TranslateWord('')).should_be(''); }, 'Passing 1234567890 Should Return Blank': function() { value_of(englishToPigLatin.TranslateWord('1234567890')).should_be(''); }, 'Passing ~!@#$%^&*()_+ Should Return Blank': function() { value_of(englishToPigLatin.TranslateWord('~!@#$%^&*()_+')).should_be(''); } }) describe('Consonant Words', { before_each : function() { englishToPigLatin = new EnglishToPigLatin(); }, 'Passing Beast Should Return Eastbay': function() { value_of(englishToPigLatin.TranslateWord('beast')).should_be('eastbay'); }, 'Passing Dough Should Return Oughday': function() { value_of(englishToPigLatin.TranslateWord('dough')).should_be('oughday'); }, 'Passing happy Should Return appyhay': function() { value_of(englishToPigLatin.TranslateWord('happy')).should_be('appyhay'); }, 'Passing question Should Return estionquay': function() { value_of(englishToPigLatin.TranslateWord('question')).should_be('estionquay'); }, 'Passing star Should Return arstay': function() { value_of(englishToPigLatin.TranslateWord('star')).should_be('arstay'); }, 'Passing three Should Return eethray': function() { value_of(englishToPigLatin.TranslateWord('three')).should_be('eethray'); } }) describe('Vowel Words', { before_each : function() { englishToPigLatin = new EnglishToPigLatin(); }, 'apple Should Return appleay': function() { value_of(englishToPigLatin.TranslateWord('apple')).should_be('appleay'); }, 'elijah Should Return elijahay': function() { value_of(englishToPigLatin.TranslateWord('elijah')).should_be('elijahay'); }, 'igloo Should Return iglooay': function() { value_of(englishToPigLatin.TranslateWord('igloo')).should_be('iglooay'); }, 'octopus Should Return octopusay': function() { value_of(englishToPigLatin.TranslateWord('octopus')).should_be('octopusay'); }, 'umbrella Should Return umbrellaay': function() { value_of(englishToPigLatin.TranslateWord('umbrella')).should_be('umbrellaay'); } }) describe('Sentences', { before_each : function() { englishToPigLatin = new EnglishToPigLatin(); }, "Passing 'hello' Should Return 'elloh'": function() { value_of(englishToPigLatin.Translate('hello')).should_be('ellohay'); }, "Passing 'hello world' Should Return 'elloh orldw'": function() { value_of(englishToPigLatin.Translate('hello world')).should_be('ellohay orldway'); }, "Passing 'hello world!' Should Return 'ellow orld!w'": function() { value_of(englishToPigLatin.Translate('hello world!')).should_be('ellohay orld!way'); }, "Passing 'Hello World' Should Return 'elloH orldW'": function() { value_of(englishToPigLatin.Translate('Hello World')).should_be('elloHay orldWay'); }, "Passing 'Hello World!' Should Return 'elloH orld!W'": function() { value_of(englishToPigLatin.Translate('Hello World!')).should_be('elloHay orld!Way'); } }) // ]]></script> </head> <body><div style="display:none;"><p>A</p><p>B</p></div></body> </html>
There are other asserts that can be made that I didn't make use of in the above same. Here are some examples... (taken from the JSSpec Manual)
- value_of('Hello'.toLowerCase()).should_be('hello');
- value_of([1,2,3]).should_be([1,2,3]);
- value_of(new Date(1979,03,27)).should_be(new Date(1979,03,27));
- value_of([]).should_be_empty();
- value_of(1 == 1).should_be_true();
- value_of(1 != 1).should_be_false();
- value_of("Hello").should_have(5, "characters");
- value_of([1,2,3]).should_have(3, "items")
- value_of({name:'Alan Kang', email:'jania902@gmail.com', accounts:['A', 'B']}).should_have(2, "accounts");
- value_of([1,2,3]).should_have_exactly(3, "items");
- value_of([1,2,3]).should_have_at_least(2, "items");
- value_of([1,2,3]).should_have_at_most(4, "items");
- value_of([1,2,3]).should_include(2);
- value_of([1,2,3]).should_not_includ
- value_of({a: 1, b: 2}).should_include("a");
- value_of({a: 1, b: 2}).should_not_include("c");
As you can see, this is a very powerful easy to read and execute testing framework. Of those frameworks I have tested thus far, I recommend it above the others (at this point I've only covered JsUnit).
Man, that is nice. Something I'd be interested to know about these testing frameworks is how easy it is to run them and report on the results as part of a continuous integration process.
ReplyDeleteHello,
ReplyDeleteNice posting.