Thursday, February 3, 2011

TDD with jsTestDriver - find image size with Java Script

In my last post I created small java script function that finds the size of image by URL. I used YUI 3 test framework for doing unit tests while I worked on that function.

Because YUI 3 test framework is in-browser framework we are facing the challenge how to automate our unit tests and how to apply TDD with automated continuous build and automated continuous integration. As we have to manually re-run tests it is a challenge to keep testing our java script on several web browsers on different platforms.

jsTestDriver is automation tool for unit testing for java script. It is a server written in Java. From several several browsers you open URL hosted on the jsTestDriver server. The jsTestDriver can then automatically re-run the unit tests whenever they are some changes in the source code and thereof shows if the unit tests failed or passed on the currently connected web browsers. That way you can test as many as you like combinations of browsers and platforms in an automated way.

I'll be working on Linux for the examples, but the jsTestDriver works on Windows too. Also, it is always a good idea to develop your java script libraries outside your web projects and ideally to use in your web projects already tested, optimized and minified java script file.

The installation of jsTestDriver is very simple. Download the jar file from the project's web site:
http://js-test-driver.googlecode.com/files/JsTestDriver-1.2.2.jar

Just copy the jar file on directory where it is convenient for you. Here is a snipped from my directory structure:

ahristov@medion:~/Desktop/Projects/js/bin$ ls
compiler.jar  JsTestDriver-1.2.2.jar  SoyToJsSrcCompiler.jar


You should export environment variable $JSTESTDRIVER_HOME (%JSTESTDRIVER_HOME% on Windows, go to My Computer -> Properties -> System -> Advanced and change the Environment Variables to make the changes permanent).

ahristov@medion:~/Desktop/Projects/js/bin$ cat ~/.bashrc | tail -n 7

export PATH=".:$HOME/bin:$PATH:/var/lib/gems/1.9.1/bin"
export JSBIN="$HOME/Desktop/Projects/js/bin"
export JSTESTDRIVER_HOME="$JSBIN"


The directory structure for our small project looks like this:

ahristov@medion:~/Desktop/Projects/js/proj/play$ ls
build  deps  jsTestDriver.conf  src  test


As I mentioned in my last post build you can ignore, I normally have build directory where I would generate minified, tested java script - the stuff that I'll be using in my web project. We don't need the deps folder this time, as we will not have any dependencies on 3rd party java script.

The file jsTestDriver.conf is a configuration file for the jsTestDriver with the following content:

ahristov@medion:~/Desktop/Projects/js/proj/play$ cat jsTestDriver.conf
server: http://localhost:4224

load:
- src/*.js
- test/*.js



It states, we'll run the jsTestDriver on port 4224, the server will load all the java script files from directories src and test. All the load paths are relative to the directory where the jsTestDriver.conf file is. Please note that the order the directories and file names are important. In our case the jsTestDriver will first load the source code java script files, then the unit test java script files. More information for the jsTestDriver configuration file you can find on the projects wiki page:

http://code.google.com/p/js-test-driver/wiki/ConfigurationFile


src - this directory holds just one java script file in which is the source code of function ImgSize()

ahristov@medion:~/Desktop/Projects/js/proj/play$ cat src/ImgSize.js
/**
* Calculate the size of image.
* @param {string} url Url of the image which size to find
* @param {function(number, number)} onSizeCalculated Callback function to run once
*   the size of the image calculated
*/
function ImgSize(url, onSizeCalculated) {

// Once the image is loaded, the calls the callback function onSizeCalculated
function setDimensions() {
// scope changed: this = Image object
if (onSizeCalculated) {
onSizeCalculated(this.width, this.height);
}
}

// Initialization:
// - sets callback on image loaded - setDimensions;
// - loads the image.
function init() {
var img = new Image();
img.name = url;
img.onload = setDimensions;
img.src = url;
}

// Run initialization
init();
};



This is the same function that we developed and tested with the YUI 3 test framework in my last post.


test is the directory where the java script files with the unit tests are


ahristov@medion:~/Desktop/Projects/js/proj/play$ ls test/
ImgSize  ImgSize.html  ImgSize-test.js
ahristov@medion:~/Desktop/Projects/js/proj/play$ ls test/ImgSize
137x38.gif



File ImgSize.html is from my last post, you can ignore it for now. The file 137x38.gif is an image with the size 137px / 38px which I'll use for the purpose of the unit testing.

If we were about to test synchronous java script we would use the traditional TestCase runner. Our ImgSize-test.js would look like:

// holds tests for the ImgSize() function
//
TestCase("ImgSizeTest", {
"test image size should be 137x38": function() {
new ImgSize('http://192.168.1.4:8080/js/proj/play/test/ImgSize/137x38.gif',
function assertOnSizeCalculated(width, height) {
assertEquals(137, width);
assertEquals( 38, height);
})
}
});



How that we are going to test asynchronous calls we should use the AsyncTestCase runner, so that our ImgSize-test.js contains:

var ImgSizeTest = AsyncTestCase('ImgSizeTest');

ImgSizeTest.prototype.testImgSizeFunction = function(queue) {

expectAsserts(2);

var onSizeCalculated = queue.add(function() {
assertEquals(137, arguments[0][0]);
assertEquals( 38, arguments[0][1]);
});

new ImgSize('http://192.168.1.4:8080/js/proj/play/test/ImgSize/137x38.gif',
onSizeCalculated);
};




The queue parameter accepts inline function via method add(), the function will run whenever the browser downloaded the image and calculated the size of the image.

As you can see, the image file should be accessible from URL from the testing browsers, so I just put the gif image on web server on the following URL above. Keep in mind, you have to adjust that the way you need it.

The syntax is very similar to YUI 3 test and even simpler. Whenever the browser loaded the image, we are going to check if the ImgSize() function gets the proper image size information. Please note that we are not dealing with DOM and UI. If you want to test DOM operations you can add HTML to the unit test files, for more information check the wiki pages of the jsTestDriver project:

http://code.google.com/p/js-test-driver/wiki/HtmlDoc


The page containing documentation for asynchronous unit tests with jsTestDriver is:
http://code.google.com/p/js-test-driver/wiki/AsyncTestCase

How it is time to start the jsTestDriver server:

ahristov@medion:~$ java -jar $JSTESTDRIVER_HOME/JsTestDriver-1.2.2.jar --port 4224


Next step is to open the URL where the jsTestDriver server is listening:



Enter the URL to the server in the web browser and click in "Capture This Browser"



This browser started waiting for automation from the server to run unit tests. Let's add another browser to the testing:



Clicking on "Capture This Browser":

Connect several web browsers to the jsTestDriver server.

As we are ready to start our unit tests, we have to first run the jsTestDriver server. From the directory where the configuration file jsTestDriver.conf is we run:

ahristov@medion:~/Desktop/Projects/js/proj/play$ java -jar $JSTESTDRIVER_HOME/JsTestDriver-1.2.2.jar --tests all


Which gets the following output:



The test passed on 3 different browsers.


Now, how to repeat the tests automatically whenever the source code of our java script changed?

If you have Ruby installed on your system, you can install the jstdutil package:


gem install jstdutil


It provides a wrapper to jsTestDriver so instead of writing every time this long command lines on the shell, you just - from the directory where the jsTestDriver.conf file is run:

ahristov@medion:~/Desktop/Projects/js/proj/play$ jsautotest


Every time we do changes on our java script source code the jsTestDriver will automatically start doing the unit tests and then will report the result:



Let's change the test, make the test fail, change the following line:

assertEquals( 38, arguments[0][1]);


to

assertEquals( 39, arguments[0][1]);


as the jsautotest is running. At the moment we save the javascript file, jsautotest automatically re-runs the tests sending the appropriate command to the jsTestDriver server and all currently captured browsers start running the tests. We get error in our tests and the jsautotest console explains this with output similar to:



Changing the assert back from 39 to 38 in the ImgSize-test.js makes jsautotest again to re-run the tests on the jsTestDr4iver server, and this time they successfully pass.


The jsTestDriver project has the following URL:
http://code.google.com/p/js-test-driver/

The home page of the jstdutil Ruby package:
http://cjohansen.no/en/javascript/jstdutil_a_ruby_wrapper_over_jstestdriver



Atanas Hristov

kick it on DotNetKicks.com
Shout it

4 comments:

  1. Hi Atanas ,
    I have a query , can we make use of jstestdriver to load the HTML page , because what I am trying to achieve is the load teh swf file that has the test cases and the result from the swf file is returned in the form of an xml.So was thinking if i can integrate the swf results with the jstestdriver either by calling the html page which will load the swf files or directly by calling it from the jstestdriver.
    Regards,
    Sajeev

    ReplyDelete
  2. Thanks for sharing your info. I really appreciate your efforts and I will be waiting for your further write ups thanks once again.
    Vee Eee Technologies| Vee Eee Technologies|

    ReplyDelete
  3. Thanks for sharing your info. I really appreciate your efforts.
    mysql services

    ReplyDelete