Monthly Archives: January 2011

Automated Javascript unit testing with JsTestDriver

Introduction

In a previous post about unit testing your Javascript, I explained how you can unit test your own Javascript library code with the QUnit test suite. Now that we can unit test our Javascript, it would be cool to automate the process. We’ll accomplish that by using JsTestDriver, a Javascript test runner, built in Java.

What is a “test runner”?

A test runner is something that runs your tests. In my previous blog post on the subject, we wrote an HTML file which then contained references to the library & scripts to run. By opening that HTML file in a web browser, we ran our tests. In this case, the test runner was the browser itself, and the HTML file was a wrapper for your tests.

Having a browser as a test runner is good, because you are then testing your code in that specific browser. However, it is tedious work to open up the HTML wrapper in each and every browser you have available. Testing takes ages this way. What would be better is some sort of “thing” that can run your test code in a lot of browsers, preferably in parallel, with as little effort as possible. That’s where JsTestDriver comes in sight.

How does JsTestDriver work?

JsTestDriver simply works by starting a server, listening on a port (usually port 4224), where you connect browsers to. All the kinds of browsers you want your code tested agains, should connect to the JsTestDriver server. JsTestDriver then “captures” your browser, and puts it available for running tests in. Then you execute your test suite, and each captured browser will execute your test code. Here’s a small schema I copied from their site:

jstestrunner_overview.png

This kind of setup makes it easier to test your code against multiple browsers. Usually you won’t have different browsers on different OS’es available. What can help, is building virtual machines in VirtualBox and installing the browsers in them. Then all you have to do is point these browsers to the same JsTestRunner server instance, and you have your own little test farm.

Setting JsTestDriver server up

First thing you need to do, is download JsTestDriver from the project page over at google. At the time of writing, the most recent version was JsTestDriver-1.2.2.jar. I have a dedicated folder for it, where I also added the Code Coverage plugin. But more on that plugin later.

Get the server running by entering the following in your shell:

	java -jar /path/to/JsTestDriver-1.2.2.jar --port 4224

Now you have to leave your shell open to keep the server running. If all went well, you should now be able to surf to http://localhost:4224 and see the following:

Screen shot 2011-01-24 at 18.46.14.png

You can now go ahead, and add your browser to the list of browsers that will execute the test suite, by clicking the link “Capture This Browser”. You should now see this in your browser:

Screen shot 2011-01-24 at 18.48.21.png

Now your browser is ready and waiting for tests to come in.

Preparing your test suite

JsTestDriver can work with multiple Javascript test suites, but adheres to the JUnit standard. Therefor, if you want to keep on using QUnit, you’ll have to use an adapter. Luckily there’s one readily available, and using the adapter is very easy.

To use the adapter, first go to http://code.google.com/p/js-test-driver/source/browse/#svn/trunk/JsTestDriver/contrib/qunit/src and download equiv.js and QUnitAdapter.js to your test suite. Next you have to tell JsTestRunner where to find these files by adding them into your config file.

For each set of Javascript unit tests you want to run, you have to create a config file for JsTestDriver. In this config file, you specify the location of your files, and optionally which plugins you want to use. Here’s my config file:

server: http://localhost:4224

load:
# Add these lines to load the equiv function and adapter in order, before the tests
- external/equiv.js
- external/QUnitAdapter.js

# Load jQuery external dependency for the TAG library:
- external/jquery.js

# Load the files we want to test:
- lib/tag.js
- lib/tag/*.js

# Load the tests:
- tests/tag.js
- tests/tag/*.js

As you can see, first I load the files necessary for the QUnit adapter. Then I load jQuery, because it’s a dependency for my own library. Next all files of my library are loaded, and lastly the unit tests. Save this file as “jsTestDriver.conf” alongside your other files.

Limitations

The QUnitAdapter does a nice job converting the QUnit tests to something JsTestDriver can understand. However, it is not possible to do asynchronous tests with JsTestDriver. This means that any test where you use the “start()” and “stop()” functionality of QUnit, you will get an error. Keep this in mind when running your tests. They may run fine when you use the browser as your test runner, but fail when you use JsTestDriver.

Running your tests

Now you have to open up a new shell, and just enter the following:

java -jar /path/to/JsTestDriver-1.2.2.jar --config /path/to/jsTestDriver.conf --tests all

If you’re familiar with PHPUnit, you should see similar output:

Screen shot 2011-01-24 at 19.09.54.png

A dot represents a passed test. An “F” means a Failed test, and “E” indicates that an error happened.

If you have more browsers installed, just go ahead and capture them by surfing to http://localhost:4224. If you then run your test suite again, you’ll see just how many of your tests passed/failed/errored in each browser.

Code coverage

Unit tests are all great, but it’s even better if you had an idea about which parts of your code were actually executed, and which parts were skipped. There is a plugin for this available at the project website. Download, and copy coverage-1.2.2.jar in a “plugins” directory alongside the JsTestDriver-1.2.2.jar file.

Now we need to add it to the jsTestDriver.conf for our test suite:

server: http://localhost:4224

load:
# Add these lines to load the equiv function and adapter in order, before the tests
- external/equiv.js
- external/QUnitAdapter.js

# Load jQuery external dependency:
- external/jquery.js

# Load the files we want to test:
- lib/tag.js
- lib/tag/*.js

# Load the tests:
- tests/tag.js
- tests/tag/*.js

# Code coverage
plugin:
- name: "coverage"
  jar: "/path/to/plugins/coverage-1.2.2.jar"
  module: "com.google.jstestdriver.coverage.CoverageModule"

With the config updated, it’s time to run the tests again. We run the same command as previously mentioned, but with the added option to specify a code coverage output folder.

java -jar /path/to/JsTestDriver-1.2.2.jar --config /path/to/jsTestDriver.conf --tests all --testOutput /path/to/coverage/output

If the specified output directory doesn’t exist yet, it will be created, and in it you will now find some XML files and a jsTestDriver.conf-coverage.dat file in the directory you specified. These files contain all the data of the coverage report, exported into the a format that can be used by other programs to show you a nice overview. If you have LCOV installed on your system, you can convert the .dat file easily to a readable HTML file by entering this command:

genhtml /path/to/jsTestDriver.conf-coverage.dat.

If you now open up the generated index.html file in your favorite browser, you get something like this:

LCOV - jsTestDriver.conf-coverage.dat_1295967277319.png

With this coverage report, you can see which parts of your code were executed during the unit tests, and more importantly, which parts weren’t. It’s up to you now, to update your unit tests to get your coverage percentage higher. We would all love to achieve 100% coverage, but I think that if you have about 70% coverage, your library is already well tested.

Conclusion

With JsTestDriver, it’s possible to quite easily automate your Javascript unit testing in more browsers. The process of capturing browsers, and then distributing the unit tests in parallel to them certainly speeds up the whole unit testing itself. Code coverage is very nice to have, as it presents you with a document you can show your client or employer.

The only downside I have discovered so far, is the lack of the ability to create asynchronous tests. Especially with so much website functionality relying on AJAX to update the interface, you really miss this feature. On the other hand, you can construct your code in such a way, that not testing AJAX functionality doesn’t necessarily result in big chunks of code without coverage. Only thing left is to get as many Virtual Machines and browsers connected to the JsTestDriver server, and get testing!

A smarter strategy for using Zend_Navigation

What I love about Zend Framework is that it has so many components you can use. One of these components that can make your life easier is Zend_Navigation. The documentation says, it’s a component “for managing trees of pointers to web-pages”. So basically you can use it for all your navigational needs on your website: menu, breadcrumbs, sitemaps, …

One site menu please, with caching

Most examples on how to use Zend_Navigation show you how to create a relatively simple site structure. The amount of pages is limited, and this is great to demonstrate the different functionalities of this component. And to be honest, most sites you’ll create will just have a simple site structure.

Sometimes, you’ll build your site menu from a database, and usually this means your site structure will be bigger than what the examples show you. One way of making sure that everything stays fast, is making use of another great Zend Framework component: Zend_Cache. By caching your site structure, you bypass the fetching of data from the database and molding them into Zend_Navigation_Page’s or -Containers. Here’s a small example:

/**
 * Site service
 *
 * @category	Service
 * @package		Site
 */
/**
 * Service for general site related stuff
 *
 * @category	Service
 * @package		Site
 */
class Application_Service_Site
{
	const USE_CACHE_TRUE = true;
	const USE_CACHE_FALSE = false;
	
	/**
	 * Returns the site navigation from the database
	 *
	 * @param boolean $useCache OPTIONAL flag to specify if caching should be used
	 * @return Zend_Navigation 
	 */
    public function getNavigation ($useCache = self::USE_CACHE_TRUE)
    {
		// if caching is enabled, try to load the navigation from the cache:
        if (self::USE_CACHE_TRUE === $useCache) {
            $cache = $this->_getCacheObject();
			if (!$container = $cache->load('sitenavigation')) {
				// cache was invalid, so regenerate the data, and cache it:
				$container = $this->getNavigation(self::USE_CACHE_FALSE);
				$cache->save($container, 'sitenavigation');
			}
			return $container;
        }
        
		// fetch data from DB:
		$results = $this->getCompleteCategoryTree();
		
		// recursively build the navigation:
		$container = $this->_buildNavigationTree($container, $results);

        return $container;
    }
	
	/**
	 * Transforms a tree of data to a tree in Zend_Navigation
	 *
	 * @param Zend_Navigation $container
	 * @param object $tree
	 * @return Zend_Navigation
	 */
	protected function _buildNavigationTree ($container, $tree)
	{
		foreach ($tree as $node) {
			$page = new Zend_Navigation_Page_Mvc(array(
				'label'         => $node->label,
				'controller'    => $node->controller,
				'action'        => $node->action,
				'params'        => unserialize($node->params)
			));
			
			if ($node->hasChildren()) {
				$page = $this->_buildNavigationTree($page, $node->getChildren());
			}
			
			$container->addPage($page);
		}
		
		return $container;
	}
	
	/**
     * Get a cache object from the cache manager
     *
     * @return Zend_Cache
     */
    protected function _getCacheObject ($cacheName)
    {
		// get the bootstrap:
		$bootstrap = Zend_Controller_Front::getInstance()->getParam('bootstrap');
		
		// get cachemanager from the bootstrap:
    	$cacheManager = $bootstrap->getResource('cachemanager');
		
		// get requested cache:
    	$cache = $cacheManager->getCache($cacheName);
		
    	return $cache;
    }
}

Never mind that the example isn’t really realistic and that the code isn’t complete. I’ve added in-line comments everywhere, so it should be self-explanatory. In short, the code fetches data from the database, and transforms it into a Zend_Navigation object. Basically, you fetch a tree of data, and recursively create a tree in Zend_Navigation for your entire site structure. Optionally, you can specify to use caching or not. For convenience, the cache is managed by the ‘cachemanager’ Application Resource. This makes it easy to have a different setup for production/development environment via your application.ini config:

resources.cachemanager.sitenavigation.frontend.name = Core
resources.cachemanager.sitenavigation.frontend.customFrontendNaming = false
resources.cachemanager.sitenavigation.frontend.options.lifetime = 1209600
resources.cachemanager.sitenavigation.frontend.options.automatic_serialization = true
resources.cachemanager.sitenavigation.backend.name = File
resources.cachemanager.sitenavigation.backend.customBackendNaming = false
resources.cachemanager.sitenavigation.backend.options.cache_dir = APPLICATION_PATH "/../tmp"
resources.cachemanager.sitenavigation.frontendBackendAutoload = false

And then it goes horribly wrong!

The example above works fine, and you just start coding away on other stuff. You fill up the database with some test data, and you’re happy that everything works just fine. But you’ve already made your first mistake: premature optimization. By defaulting to using cache, you don’t really know how fast or slow things are without using a cache mechanism. But no worries, let’s assume you didn’t make that mistake, and you turn on caching after you’ve evaluated the current site speed, and you see that caching really makes things faster.

What went horribly wrong for me is that by using the cache, I saw the page loading time go down from 4 seconds to 1.8 seconds. That’s quite a speed bump, but 1.8 seconds still is slow. And since there was no other optimization option available to me, I had to start looking in the code. Turns out that my navigation was a big time consumer. If I totally disabled my navigation, the page loading time went down to 0.9 seconds. So there was definitely something fishy going on there.

Who needs a big tree anyway?

A bonsai is nice too! Sorry, bad joke. But that’s exactly what was wrong: There was so much data in the database, the navigation tree just became too big. Everything was read from the file cache and loaded into the memory. On each request. Ouch! That hurt the server (and my ego). Loading more than 10.000 pages into memory isn’t that efficient. And when you think about it, who really needs the entire tree in memory on each request anyway? No-one does, that’s who.

My fault was assuming that the practices that work for small and static site navigations would just work for large and dynamic site navigations. My last project was a website for a company that offers trainings and courses. There were more than 6.000 courses, nicely put in a category structure of sometimes 3 levels deep. For the navigation, I always needed the first level of categories, optionally the subcategories of a selected category, and optionally a selected course. This came down to 30′ish pages I needed. Compare that to the more than 10.000 I initially loaded into the memory.

How do you start optimizing your code so you end up with a Zend_Navigation container that’s populated depending on your request, instead of just loading everything? Quite simple really, by just passing some extra parameters from the request to the method that builds the navigation. Here’s an example:

/**
 * Site service
 *
 * @category	Service
 * @package		Site
 */
/**
 * Service for general site related stuff
 *
 * @category	Service
 * @package		Site
 */
class Application_Service_Site
{
	const USE_CACHE_TRUE = true;
	const USE_CACHE_FALSE = false;
	
	/**
	 * Returns the site navigation from the database
	 *
	 * @return Zend_Navigation 
	 */
    public function getNavigation (array $params)
    {
		// fetch data from DB:
		$results = $this->getCategoriesAndBranchForParams($params);
		
		// recursively build the navigation:
		$container = $this->_buildNavigationTree($container, $results);

        return $container;
    }
	
	/**
	 * Get the path in the tree that leads from the root to current category
	 *
	 * @param array $params 
	 * @return array
	 */
	public function getCategoriesAndBranchForParams (array $params)
	{
		// get the first level of categories:
		$categories = $this->_getCategoriesOnLevel(0);
		
		foreach ($categories as &$category) {
			if ($category->getHasChildWithId((int) $params['category_id'])) {
				// get the leaf of the tree we're currently on:
				$leafNode = $this->_getCategoryById($params["category_id"]);
				
				// get the path from the leaf to the root:
				$category = $this->_retraceToNode($leafNode, $category);
			}
		}
		
		return $categories;
	}
	
	/**
	 * Recursively retrace the path from given node to the root of the tree
	 *
	 * @param object $node
	 * @return object
	 */
	protected function _retraceToNode ($node, $rootNode)
	{
		if (null !== $node->getParentNodeId()) {
			$parent = $this->_getCategoryById($node->getParentNodeId());
			$parent->addChild($node);
			
			if ($parent->getCategoryId() != $rootNode->getCategoryId()) {
				// we haven't reached our desired $rootNode yet, so continue
				return $this->_retraceToRoot($parent, $rootNode);
			}
		}
		
		return $node;
	}
	
	/**
	 * Returns the navigation from the cache
	 *
	 * @return Zend_Navigation
	 */
	public function getCachedNavigation ()
	{
		$cache = $this->_getCacheObject();
		if (!$container = $cache->load('sitenavigation')) {
			// cache was invalid, so regenerate the data, and cache it:
			$container = $this->getNavigation();
			$cache->save($container, 'sitenavigation');
		}
		return $container;
	}
	
	/**
	 * Transforms a tree of data to a tree in Zend_Navigation
	 *
	 * @param Zend_Navigation $container
	 * @param object $tree
	 * @return Zend_Navigation
	 */
	protected function _buildNavigationTree ($container, $tree)
	{
		foreach ($tree as $node) {
			$page = new Zend_Navigation_Page_Mvc(array(
				'label'         => $node->label,
				'controller'    => 'category',
				'action'        => 'overview',
				'params'        => unserialize($node->params)
			));
			
			if ($node->hasChildren()) {
				$page = $this->_buildNavigationTree($page, $node->getChildren());
			}
			
			$container->addPage($page);
		}
		
		return $container;
	}
	
	/**
     * Get a cache object from the cache manager
     *
     * @return Zend_Cache
     */
    protected function _getCacheObject ($cacheName)
    {
		// get the bootstrap:
		$bootstrap = Zend_Controller_Front::getInstance()->getParam('bootstrap');
		
		// get cachemanager from the bootstrap:
    	$cacheManager = $bootstrap->getResource('cachemanager');
		
		// get requested cache:
    	$cache = $cacheManager->getCache($cacheName);
		
    	return $cache;
    }
}

First, but least important: the caching has been moved to a dedicated method called “getCachedNavigation”. Personally, I find it more readable than working with flags.

Secondly, and much more important, a parameter has been introduced in the “getNavigation” method. This parameter is what you would get from the request object, and contains the controller name, action name, and query parameters. This query parameter is then used to fetch the part of the tree that’s relevant for current page. Instead of fetching the entire tree, we only fetch the first level of categories, and 1 branch we need in order to determine the correct breadcrumb path. All the rest is irrelevant.

A thousand words

A picture often says more than a thousand words. Here’s how I have reduced the memory footprint of my navigation, by simply keeping in mind which page is being looked at.

navigation_tree.png

This is just a small representation of what was originally in memory for the Zend_Navigation object. Imagine each level 1 category to have at least 2 second level categories. Each second level category has at least 10 pages. After optimizing, the result is:

navigation_branch.png

As you can see, the end result is a much simpler and smaller tree. I have kept the first level of categories, as I needed them for my menu. Then I have 1 branch which reaches out to the page I’m currently on. This branch is used for the breadcrumbs.

Conclusion

Needless to say that going from a very big tree in memory to just a very small tree will dramatically impact your site’s responsiveness. This optimization roughly translated into a 50% speed boost for my project. Best of all, it took only 1 hour of work to achieve this. I have learned from this, that caching should never be implemented and used from the beginning. It’s a premature optimization mistake which can lead us to think that something else is the bottleneck, not the code. Taking away the cache forced me to actually think again about the code I had written. Sometimes improvements are obvious, if you open up your mind for them.