15 min read

(For more resources related to this topic, see here.)

Types of testing

Testing can happen on many different levels. From the code level to integration and even testing individual functions of the user-facing implementation of an enterprise application, there are numerous tools and techniques to test your application. In particular, we will cover the following:

  • Unit testing

  • Functional testing

  • Browser testing

Black box versus white box testing

Testing is often talked about within the context of black box versus white box testing. This is a useful metaphor in understanding testing at different levels. With black box testing, you look at your application as a black box knowing nothing of its internals—typically from the perspective of a user of the system. You simply execute functionality of the application and test whether the expected outcomes match the actual outcomes. White box differs from black box testing in that you know the internals of the application upfront and can thus pinpoint failures directly and test for specific conditions. In this case, you simply feed in data into specific parts of the system and test whether the expected output matches the actual output.

Unit testing

The first level of testing is at the code level. When you are te sting specific and individual units of code on whether they meet their stated goals, you are unit testing. Unit testing is often talked about in conjunction with test-driven development, the practice of writing unit tests first and then writing the minimal amount of code necessary to pass those tests. Having a suite of unit tests against your code and employing test-driven processes—when done right—can keep your code focused and help to ensure the stability of your enterprise application.

Typically, unit tests are set up in a separate folder in your codebase. Each test case is composed of the following parts:

  • Setup to build the test conditions under which the code or module is being tested

  • An instantiation and invocation of the code or module being tested

  • A verification of the results returned

Setting up your unit test

You usually start by setting up your test data. For example, if you are testing a piece of code that requires an authenticated account, you might consider creating a set of test users of your enterprise application. It is advisable that your test data be coupled with your test so that your tests are not dependent on your system being in a specific state.

Invoking your target

Once you have set up your test data and the conditions in which the code you are testing needs to run, you are ready to invoke it. This can be as simple as invoking a method.

Mocking is a very important concept to understand when unit testing. Consider a set of unit tests for a business logic module that has a dependency on some external application programming interface (API). Now imagine if the API goes down. The tests would fail. While it is nice to get an indication that the API you are dependent upon is having issues, a failing unit test because of this is misleading because the goal of the unit test is to test the business logic rather than external resources on which you are dependent. This is where mock objects come into the picture. Mock objects are stubs that replicate the interface of a resource. They are set up to always return the same data the external resource would under normal conditions. This way you are isolating your test to just the unit of code you are testing.

Mocking employs a pattern called dependency injection or inversion of control. Sure, the code you are testing may be dependent on an external resource. Yet how will you swap it in a mock resource? Code that is easy to unit test allows you to pass in or “inject” these dependencies when invoking it.

Dependency injection is a design pattern where code that is dependent on an external resource has that dependency passed into it thereby decoupling your code from that dependency. The following code snippet is difficult to test since the dependency is encapsulated into the function being tested. We are at an impasse.

var doSomething = function() {
var api = getApi();
//A bunch of code
api.call();
}
var testOfDoSomething = function() {
var mockApi = getMockApi();
//What do I do now???
}

The following new code snippet uses dependency injection to circumvent the problem by instantiating the dependency and passing it into the function being tested:

var doSomething = function(api) {
//A bunch of code
api.call();
}
var testOfDoSomething = function() {
var mockApi = getMockApi();
doSomething(mockApi);
}

In general, this is good practice not just for unit testing but for keeping your code clean and easy to manage. Instantiating a dependency once and injecting where it is needed makes it easier to change that dependency if the need occurs. There are many mocking frameworks available including JsMockito (http://jsmockito.org/ ) for JavaScript and Mockery (https://github.com/padraic/mockery)for PHP.

Verifying the results

Once you have invoked the code being tested, you need to capture the results and verify them. Verification comes in the form of assertions. Every unit testing framework comes with its own set of assertion methods, but the concept is the same: take a result and test it against an expectation. You can assert whether two things are equal. You can assert whether two things are not equal. You can assert whether a result is a valid number of a string. You can assert whether one value is greater than another. The general idea is you are testing actual data against your hypothesis. Assertions usually bubble up to the framework’s reporting module and are manifested as a list of passed or failed tests.

Frameworks and tools

A bevy of tools have arisen in the past few years that aid in unit testing of JavaScript. What follows is a brief survey of notable frameworks and tools used to unit test JavaScript code.

JsTestDriver

JsTestDriver is a framework built at Google for unit testing. It has a server that runs on multiple browsers on a machine and will allow you to execute test cases in the Eclipse IDE.

This screenshot shows the results of JsTestDriver. When run, it executes all tests configured to run and displays the results.

More information about JsTestDriver can be found at http://code.google.com/p/js-test-driver/.

QUnit

QUnit is a JavaScript unit testing framework created by John Resig of jQuery fame. To use it, you need to create only a test harness web page and include the QUnit library as a script reference. There is even a hosted version of the library. Once included, you need to only invoke the test method, passing in a function and a set of assertions. It will then generate a nice report.

Although QUnit has no dependencies and can test standard JavaScript code, it is oriented around jQuery. More information about QUnit can be found at http://qunitjs.com/.

Sinon.JS

Often coupled with QUnit, Sinon.JS introduces the concept of spying wherein it records function calls, the arguments passed in, the return value, and even the value of the this object. You can also create fake objects such as fake servers and fake timers to make sure your code tests in isolation and your tests run as quickly as possible. This is particularly useful when you need to make fake AJAX requests.

More information about Sinon.JS can be found at http://sinonjs.org/.

Jasmine

Jasmine is a testing framework based on the concept of behavior-driven development. Much akin to test-driven development, it extends it by infusing domain-driven design principles and seeks to frame unit tests back to user-oriented behavior and business value. Jasmine as well as other behavior-driven design based frameworks build test cases—called specs—using as much English as possible so that when a report is generated, it reads more naturally than a conventional unit test report.

More information about Jasmine can be found at http://pivotal.github.com/jasmine/.

Functional testing

Selenium has become the name in website functional testing. Its browser automation capabilities allow you to record test cases in your favorite web browser and run them across multiple browsers. When you have this, you can automate your browser tests, integrate them with your build and continuous integration server, and run them simultaneously to get quicker results when you need them.

Selenium includes the Selenium IDE, a utility for recording and running Selenium scripts. Built as a Firefox add-on, it allows you to create Selenium test cases by loading and clicking on web pages in Firefox. You can easily record what you do in the browser and replay it. You can then add tests to determine whether actual behavior matches expected behavior. It is very useful for quickly creating simple test cases for a web application. Information on installing it can be found at http://seleniumhq.org/docs/02_selenium_ide.html.

The following screenshot shows the Selenium IDE. Click on the red circle graphic on the right-hand side to set it to record, and then browse to http://google.com in the browser window and search for “html5”. Click on the red circle graphic to stop recording. You can then add assertions to test whether certain properties of the page match expectations. In this case, we are asserting that the text of the first link in the search results is for the Wikipedia page for HTML5. When we run our test, we see that it passes (of course, if the search results for “html5” on Google change, then this particular test will fail).

Selenium includes WebDriver, an API that allows you to drive a browser natively either locally or remotely. Coupled with its automation capabilities, WebDriver can run tests against browsers on multiple remote machines to achieve greater scale.

For our MovieNow application, we will set up functional testing by using the following components:

  • The Selenium standalone server

  • The php-webdriver connector from Facebook

  • PHPUnit

The Selenium standalone server

The Selenium standalone server routes requests to the HTML5 application. It needs to be started for the tests to run. It can be deployed anywhere, but by default it is accessed at http://localhost:4444/wd/hub. You can download the latest version of the standalone server at http://code.google.com/p/selenium/downloads/list or you can fire up the version included in the sample code under the test/lib folder. To start the server, execute the following line via the command line (you will need to have Java installed on your machine):

java -jar lib/selenium-server-standalone-#.jar

Here, # indicates the version number.

You should see something akin to the following:

At this point, it is listening for connections. You will see log messages here as you run your tests. Keep this window open.

The php-webdriver connector from Facebook

The php-webdriver connector serves as a library for WebDriver in PHP. It gives you the ability to make and inspect web requests using drivers for all the major web browsers as well as HtmlUnit. Thus it allows you to create test cases against any web browser. You can download it at https://github.com/facebook/php-webdriver.We have included the files in the webdriver folder.

PHPUnit

PHPUnit is a unit testing framework that provides the constructs necessary for running our tests. It has the plumbing necessary for building and validating test cases. Any unit testing framework will work with Selenium; we have chosen PHPUnit since it is lightweight and works well with PHP. You can download and install PHPUnit any number of ways (you can go to http://www.phpunit.de/manual/current/en/installation.html for more information on installing it). We have included the phpunit.phar file in the test/lib folder for your convenience. You can simply run it by executing the following via the command line:

php lib/phpunit.phar <your test suite>.php

To begin, we will add some PHP files to the test folder. The first file is webtest. php. Create this file and add the following code:

<?php
require_once "webdriver/__init__.php";
class WebTest extends PHPUnit_Framework_TestCase {
protected $_session;
protected $_web_driver;
public function __construct() {
parent::__construct();
$_web_driver = new WebDriver();
$this->_session = $_web_driver->session('firefox');
}
public function __destruct() {
$this->_session->close();
unset($this->_session);
}
}
?>

The WebTest class integrated WebDriver into PHPUnit via the php-webdriver connector. This will serve as the base class for all of our test cases. As you can see, it starts with the following:

require_once "webdriver/__init__.php";

This is a reference to __init__.php in the php-webdriver files. This brings in all the classes needed for WebDriver. In the constructor, WebTest initializes the driver and session objects used in all test cases. In the destructor, it cleans up its connections.

Now that we have everything set up, we can create our first functional test. Add a file called generictest.php to the test folder. We will import WebTest and extend that class as follows:

<?php
require_once "webtest.php";
class GenericTest extends WebTest {
}
?>

Inside of the GenericTest class, add the following test case:

public function testForData() {
$this->_session->open('http://localhost/html5-book/Chapter%2010/');
sleep(5); //Wait for AJAX data to load
$result = $this->_session->element("id", "movies-near-me")->text();
//May need to change settings to always allow sharing of location
$this->assertGreaterThan(0, strlen($result));
}

We will open a connection to our application (feel free to change the URL to wherever you are running your HTML5 application), wait 5 seconds for the initial AJAX to load, and then test for whether the movies-near-me div is populated with data.

To run this test, go to the command line and execute the following lines:

chmod +x lib/phpunit.phar
php lib/phpunit.phar generictest.php

You should see the following:

This indicates that the test is passed. Congratulations! Now let us see it fail. Add the following test case:

public function testForTitle() {
$this->_session->open('http://localhost/html5-book/Chapter%2010/');
$result = $this->_session->title();
$this->assertEquals('Some Title', $result);
}

Rerun PHPUnit and you should see something akin to the following:

As you can see, it was expecting ‘Some Title’ but actually found ‘MovieNow’. Now that we have gotten you started, we will let you create your own tests. Refer to http://www.phpunit.de/manual/3.7/en/index.html for guidance on the different assertions you can make using PHPUnit.

More information about Selenium can be found at http://seleniumhq.org/.

Browser testing

HTML5 enterprise applications must involve actually looking at the application on different web browsers. Thankfully, many web browsers are offered on multiple platforms. Google Chrome, Mozilla Firefox, and Opera all have versions that will install easily on Windows, Mac OSX, and flavors of Linux such as Ubuntu. Safari has versions for Windows and Mac OSX, and there are ways to install it on Linux with some tweaking.

Nevertheless, Internet Explorer can only run on Windows. One way to work around this limitation is to install virtualization software. Virtualization allows you to run an entire operating system virtually within a host operating system. It allows you to run Windows applications on Mac OSX or Linux applications on Windows. There are a number of notable virtualization packages including VirtualBox, VMWare Fusion, Parallels, and Virtual PC.

Although Virtual PC runs only on Windows, Microsoft does offer a set of prepackaged virtual hard drives that include specific versions of Internet Explorer for testing purposes. See the following URLs for details: http://www.microsoft. com/en-us/download/details.aspx?id=11575.

Another common way to test for compatibility is to use web-based browser virtualization. There are a number of services such as BrowserStack (http://www.browserstack.com/), CrossBrowserTesting (http://crossbrowsertesting.com/), and Sauce Labs (https://saucelabs.com/) that offer a service whereby you can enter a URL and see it rendered in an assortment of web browsers and platforms (including mobile) virtually through the web. Many of them even work through a proxy to allow you to view, test, and debug web applications running on your local machine.

Continuous integration

With any testing solution, it is important to create and deploy your builds and run your tests in an automated fashion. Continuous integration solutions like Hudson, Jenkins, CruiseControl, and TeamCity allow you to accomplish this. They merge code from multiple developers, and run a number of automated functions from deploying modules to running tests. They can be invoked to run on a schedule basis or can be triggered by events such as a commitment of code to a code repository via a postcommit hook.

Summary

We covered several types of testing in this article including unit testing, functional testing, and browser testing. For each type of testing, there are many tools to help you make sure that your enterprise application runs in a stable way, most of which we covered bar a few. Because every minute change to your application code has the potential to destabilize it, we must assume that that every change does. To ensure that your enterprise applications remain stable and with minimal defect, having a testing strategy in place with a rich suite of tests—from unit to functional—combined with a continuous integration server running those tests is essential. One must, of course, weigh the investment in time for writing and executing tests against the time needed for writing production code, but the savings in long-term maintenance costs can make that investment worthwhile.

Resources for Article :


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here