Showing posts with label TDD. Show all posts
Showing posts with label TDD. Show all posts

May 31, 2011

Maven + JavaScript Unit Test using JsTestDriver

What are we doing?
You wake up to the annoying Justin Timberlake ringtone you set for the office only to discover it's the site is on fire" call. A quick glance at the alarm clock and you realize it's 3am. It's this time when you decide that you need to get some test coverage on your Java Script.

What is Unit Testing?
Unit Testing is a method by which individual units of source code are tested to determine if they are fit for use. A unit is the smallest testable part of an application and in JavaScript, typically it is a function.

Why would you want to unit test JavaScript?
JavaScript has grown from the little scripts that would change button images on rollover to a fully functional, and often application critical, complex applications. There are many interactions with DOM objects, AJAX request/response and user events that make testing your code a must-have. Unit testing will give you the the confidence to add new features and change existing code. In addition, unit test self-document the code so you can be sure that the rad new function you wrote does exactly what you want it to.

What is covered
What the rest of this blog will describe is one approach to integrate maven and JsTestDriver for local development. In a near future blog, this approach will be extended to use on a Linux/Unix based system for a continuous integration system.

Approach
There are many approaches to unit testing JavaScript (QUnit being one of the many), however, most of the unit test frameworks do not easily allow you to integrate your unit test as a part of a maven testing cycle. The one problem with all JavaScript unit testing frameworks is that you need to have a place to run your test. At the time of this blog, there is not a "headless" browser, thus you (or your framework) must open a browser, accept JavaScript loading and running the test cases while providing repeatable testing output. This blog series will discuss a few different methods to run unit test, including a method to run in a virtual framebuffer for continuous integration systems that do not allow you to open a browser in the default display.

What is JsTestDriver?
JsTestDriver is an small application that controls your test cases, your source files and the interaction between the browsers you choose to test against. JsTestDriver allows you to only load the source files you want, along with only the DOM elements you need to unit test your source code, in other words, the browser ONLY loads your JavaScript files and optionally HTML and/or DOM elements needed by your test.

How to integrate with Maven
The approach here is to add JavaScript testing via a profile, so you can create a CI build specifically for your JavaScript unit test.

  1. JsTestDriver Setup

    This blog will not discuss in depth how to setup and run JsTestDriver. For more in-depth information, please visit http://code.google.com/p/js-test-driver/ website. This blog is using JsTestDriver 1.3.2.

    In order to load your test scripts, you must create a configuration file. For a maven application, place your configuration file in your "/resources" folder.

    Here is an example properties file (jsTestDriver.conf)

    server: http://localhost:4220
    
    load:
     # Header JS Files, all files are loaded top-down
     - main-js/lib/jquery.min.js
     # Body JS files
     - main-js/Person.js
     # Test files
     - test-js/*.js
    
    exclude:
     - main-js/exclude.js
    
    

    The above configuration file assumes your file structure is similar to:
    /src/main/webapp/scripts/*.js
    /src/test/webapp/resources/jsTestDriver.conf
    /src/test/webapp/scripts/*.js

    (NOTE: You might notice that the file path is different than the "main-js". Keep reading!)

  2. Maven Dependencies and Profile

    Next, let's add the dependencies to the POM file in your project.

    Add the following to your "dependencies" section in your pom.xml
    <dependency>
     <groupid>com.google.jstestdriver</groupId>
     <artifactid>jstestdriver</artifactId>
     <version>1.2.2</version>
     <scope>test</scope>
     <type>jar</type>
    </dependency>
    
    

    Next, we need to add a profile, which will do quite a few things. First, the JsTestDriver jar file will be copied from the repository to a known folder in the /target folder for use when running the JsTestDriver server. Next, we need to copy the JavaScript files from your project's location to a known folder in the "/test" folder, and also to match the "main-js/*" and "test-js/*" folder structures that we setup in the jsTestDriver.conf file. Lastly, we need to startup the JsTestDriver server so we can run our test.

    Here is a profile to start with:

    <profile>
        <id>local-jstests</id>
        <build>
            <plugins>
    
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-dependency-plugin</artifactId>
                    <version>2.1</version>
                    <executions>
                        <execution>
                            <id>copy</id>
                            <phase>generate-resources</phase>
                            <goals>
                                <goal>copy</goal>
                            </goals>
                            <configuration>
                                <artifactItems>
                                    <artifactItem>
                                        <groupId>com.google.jstestdriver</groupId>
                                        <artifactId>jstestdriver</artifactId>
                                        <version>${js-test-driver.version}</version>
                                        <type>jar</type>
                                        <overWrite>true</overWrite>
                                        <destFileName>jsTestDriver.jar</destFileName>
                                    </artifactItem>
                                </artifactItems>
                                <outputDirectory>${project.build.directory}/jstestdriver</outputDirectory>
                                <overWriteReleases>false</overWriteReleases>
                                <overWriteSnapshots>true</overWriteSnapshots>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
    
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <configuration>
                        <skipTests>true</skipTests>
                    </configuration>
                </plugin>
    
                <plugin>
                    <artifactId>maven-resources-plugin</artifactId>
                    <version>2.4.3</version>
                    <executions>
                        <execution>
                            <id>copy-main-files</id>
                            <phase>generate-test-resources</phase>
                            <goals>
                                <goal>copy-resources</goal>
                            </goals>
                            <configuration>
                                <outputDirectory>${basedir}/target/test-classes/main-js</outputDirectory>
                                <resources>
                                    <resource>
                                        <directory>src/main/webapp/scripts</directory>
                                        <filtering>false</filtering>
                                    </resource>
                                </resources>
                            </configuration>
                        </execution>
                        <execution>
                            <id>copy-test-files</id>
                            <phase>generate-test-resources</phase>
                            <goals>
                                <goal>copy-resources</goal>
                            </goals>
                            <configuration>
                                <outputDirectory>${basedir}/target/test-classes/test-js</outputDirectory>
                                <resources>
                                    <resource>
                                        <directory>src/test/webapp/scripts</directory>
                                        <filtering>false</filtering>
                                    </resource>
                                </resources>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
    
                <plugin>
                    <artifactId>maven-antrun-plugin</artifactId>
                    <version>1.6</version>
                    <executions>
                        <execution>
                            <phase>test</phase>
                            <configuration>
    
                                <target>
                                    <!-- make output folder -->
                                    <mkdir dir="${basedir}/target/js-test-results"/>
                                    <!-- start jsTestDriver server -->
                                    <exec executable="java">
                                        <arg line="-jar ${basedir}/target/jstestdriver/jsTestDriver.jar"/>
                                        <arg line="--port 4220"/>
                                        <!-- Possibly set this up to point to the same location of your surefire output -->
                                        <arg line="--testOutput ${basedir}/target/js-test-results"/>
                                        <arg line="--config ${basedir}/target/test-classes/jsTestDriver.conf"/>
                                        <arg line="--browser /opt/google/chrome/google-chrome"/>
                                        <arg line="--tests all"/>
                                        <arg line="--verbose"/>
                                        <arg line="--captureConsole"/>
                                        <arg line="--preloadFiles"/>
                                    </exec>
                                </target>
                            </configuration>
                            <goals>
                                <goal>run</goal>
                            </goals>
                        </execution>
                    </executions>
    
                </plugin>
            </plugins>
        </build>
    </profile>
    
    

  3. Sample Unit Test

    Now you need to have a sample JavaScript file and a corresponding unit test.

    Here is a simple JavaScript file (/src/main/webapp/scripts/Person.js)

    NAMESPACE = {};
     
    NAMESPACE.Person = function(initObj) {
     this.name = initObj.name;
     this.age = initObj.age;
     this.address = initObj.address;
     this.sex = initObj.sex;
     this.significantOther = initObj.significantOther;
     
     function setSignificantOther(person) {
      this.significantOther = person;
     }
     
     this.setSignificantOther = setSignificantOther;
    }
    

    And a sample test case might be: (/src/test/webapp/scripts/PersonTest.js

    TestCase("PersonTest", {
     testA:function(){
      expectAsserts(1);
    
       var peterGriffin = new NAMESPACE.Person({
        name: "Peter Griffin",
        age: 43,
        address: {
         address1: "31 Spooner Street",
         address2: "",
         city: "Quhog",
         state:"RI"
        },
        sex: "Male",
        significantOther: null
       });
       
       assertNotNull("The variable should not be null", peterGriffin);
      };
     }
    });
    
    
  4. Running your test

    In order to run your test, you can now type:

    mvn clean test -Plocal-js


Please stay tuned for the expansion to this on how to run in a virtual framebuffer for a CI environment (and of course, updates for found errors)

November 17, 2010

Unit Testing Named Queues: Spring 3+maven2+Google App Engine

Intro
Problem, you have a task that you know can take more than 30 seconds to complete, what do you do?  What if this task needs to be triggered every day at a specific time?  Google provides several mechanisms to to solve just this problem, queues and scheduled task, respectively.

Queues
First, let's explore the "queue" system implemented in GAE.  Note, at this time, the "queue" is still experimental which means that the API can change around some, so make sure you have strong unit testing, but don't worry, we'll cover that a bit further down the page.  To implement queues, you need to let the web app know that your expecting queues to be used, and to do this, you need to create a file named queue.xml in your WEB-INF folder (example: /src/main/webapp/WEB-INF/queue.xml).  The queue.xml file has a specific layout and that will not be covered in depth in this post.  Please read up on the queue.xml structure on Google's documentation.  We will expand on the knowledge since the documentation is not complete.

Scheduled Task
Scheduled task are task that will trigger by GAE at repeatable times, emulating linux cron, or Quartz application.  To implement a cron task, simply create a file called cron.xml and place it in your WEB-INF folder (example:/src/main/webapp/WEB-INF/cron.xml).  This post will not go into detail on the implementation of scheduled task so please check Google's documentation to get a firm understanding before reading on. In order to not pack too much into one post, Scheduled Task will be covered in the next post.

Our Problem
The scenario we're going to explain is: You have an birthday tracking software and you want a user's friends to be notified, via email, that their friend is having a birthday.  Assume that the code for the services named are tested and fully working.

To solve this we will use a queue and the scheduled task system provided by Google App Engine.

First, let's tackle the queue portion, then we will create the scheduled task.  Google's documentation on unit testing queues is very limited and only shows how to retrieve the "default" queue.  Since we could have multiple task in our system that need to be run at different rates, we will want to create a new Queue.  Let's open (or create) the queue.xml file and insert the following:
<?xml version="1.0" encoding="UTF-8"?>
<queue-entries>
    <queue>
        <name>birthdayemail</name>
        <rate>10/s</rate>
        <bucket-size>10</bucket-size>
    </queue>
</queue-entries>


Next, let's create a simple service (Note: assume that the Autowired resources are working and imports are included at the top of the file, this has been reduced to emphasize the solution)

@Service("birthdayService")
public class BirthdayServiceImpl implements BirthdayService {

    // this is the same name as in your queue.xml file
    private static final String EVENT_REMINDER_TASK_NAME = "birthdayemail";
    
    @Autowired
    private BirthdayDAO birthdayDAO;

    public List handleGuestEmailsForBirthday(Date date) {
        if (date == null) {
            throw new IllegalArgumentException("Date can not be null");
        }
        List<Guest> guestBirthdays = birthdayDAO.getByDate(date);
        for (Guest guest : guestBirthdays) {
            String TASK_URL = "/queue/birthday-emails/" + guest.getId()
            final TaskOptions taskOptions = TaskOptions.Builder.url(TASK_URL);
            // create a unique task name, note, must conform to [a-z] regex
            taskOptions.taskName(guest.getName() + "Task");
            taskOptions.method(TaskOptions.Method.POST);

            Queue queue = QueueFactory.getQueue(EVENT_REMINDER_TASK_NAME);
            // commented out to show the default queue shown in the docs
            //Queue queue = QueueFactory.getDefaultQueue();
            queue.add(taskOptions);
        }
        return events;
    }
}

Let's look at what's here:
First, we have to create a URL that will be run when the task is implemented. This URL will handle the unit of work specified, and should run in under 30 seconds per GAE's restrictions. We will not cover what this servlet does, just assume it uses the ID given in the URL path, pulls up the Guest, then uses that to get the Guest's friends' emails; then uses that to construct an email and pass to an EmailService. (HINT: the EmailService could also implement a queue so each email is separated out to run individually)

Next, notice that we are implementing a "Task Name". The this is optional, but does help when debugging to figure out what task is failing or what is running.

Now we're on to the TaskOptions. TaskOptions are a helper function used to combine URL and model data to pass on to the servlet handling the queue. Just a suggestion, but to follow the REST ideals, setting the RequestMethod to POST or PUT is advisable, depending on what you are trying to do. All Task should be idempotent, meaning that if the task fails or is interrupted, the task can be run again and not harm the data.

Lastly, we have the QueueFactory retrieving the Named Queue (the same name in the queue.xml file). Simply add your taskOptions object to the queue.add and move on.

Unit Testing Named Queues
Next, let's setup a unit test to test our named queue.  The Google documentation does not explicitly explain how to properly setup a test case for one, datastore aware context and named queries.  Below is an example of a test case written to test our Named Queries.  (Note, as before, imports have been removed and the services used have been used before.  This is not intended to be the end-all-be-all for unit testing GAE, please refer to previous post for more help, and yes, you might be able to use JMock or Mockito instead of persisting data, but I have not explored the option).

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"/testApplicationContext.xml"})
public class QueueBirthdayServiceTest
        extends AbstractJUnit4SpringContextTests {

    @Autowired
    private BirthdayService birthdayService;
    private final LocalServiceTestConfig[] configs = {
        new LocalDatastoreServiceTestConfig(),
    /* NOTE: THE QUEUE XML PATH RELATIVE TO WEB APP ROOT, More info below */
        new LocalTaskQueueTestConfig()
                .setQueueXmlPath("src/test/resources/test-queue.xml")};
    private final LocalServiceTestHelper helper
            = new LocalServiceTestHelper(configs);

    @Before
    public void setUp() {
        helper.setUp();
    }

    @After
    public void tearDown() {
        helper.tearDown();
    }

    @Test
    public void testHandleBirthdayQueueEmails() throws InterruptedException {
        // setup data
        Date birtdayDate = DateUtils.parse("10/16/1900");
        // assume this builds the data for guest/guest's friends
        int guestWithBD = 5; // # of birthdays on date
        int gPerBirthday = 10; // # of emails being sent per birthday
        setupDataForBirthdays(birthdayDate, guestWithBD, gPerBirthday);

        List<Guest> birthdayOnDay = birthdayService.
                handleGuestEmailsForBirthday(birthdayDate);
        assertEquals(guestWithBD, birthdayOnDay.size());
        // pause for a moment to allow queue to fill from previous statment
        Thread.sleep(1000);
        // verify # of birthdays with that day's expire date
        LocalTaskQueue ltq = LocalTaskQueueTestConfig.getLocalTaskQueue();
        final Queue queue = QueueFactory
                .getQueue(BirthdayService.EVENT_REMINDER_TASK_NAME);
        //final Queue queue = QueueFactory.getDefaultQueue();
        QueueStateInfo qsi = ltq.getQueueStateInfo()
                .get(queue.getQueueName());
        assertNotNull(qsi);
        int expectedTaskCount = guestWithBD*gPerBirthday;
        assertEquals(expectedTaskCount, qsi.getTaskInfo().size());
        assertEquals(birthdayOnDay.get(0).getID() + "Task",
                qsi.getTaskInfo().get(0).getTaskName());

    }
}
NOTE: If the syntax or unit testing is very foreign to you, please visit my previous post on Unit Testing on Google App Engine.

Let's explore more
The first half is not too exiting, just setting up data and calling the service that creates the queue.  We are creating 5 guest with birthdays on the given date, and each of those 5 guest have 10 friends who we intend to email.  This service is already "written" out (above)

What you need to pay attention to is at the top of the file, during the setup of the "helper", we have constructed it with a LocalDatastoreServiceTestConfig and LocalTaskQueueTestConfig objects. The second, the "LocalTaskQueueTestConfig" is the important one to add when using a queue. If you are not using the DefaultQueue, you will need to explicitly state where the queue.xml file is. I suggest that you create a test-queue.xml file and place it in your /src/test/resources folder, so as to not mix production data and testing. NOTE: This file is loaded based relative to the ROOT application folder. The rest of the test should be pretty self explanatory.

In the next post, we will uncover how to wire the service up to a Scheduled Task so you can automate the emails being sent. Stay tuned!

September 15, 2010

Spring 3 + maven2 + Google App Engine: Part 6 [Integration Testing using HttpUnit]

In my previous post spring3 + maven2 + Google App Engine: Part 5[Unit Testing] I demonstrated how to create unit test for your Google App Engine application. Unit testing is only one part of the goal of 100% test coverage. The next major topic to cover, and arguably the most difficult to setup is integration tests. Integration test are test that use more than one component to complete the test. In web application testing, this is typically where test that can only be run when there is a running application server.

This post will cover Integration test using HttpUnit and the Failsafe Maven Plugin. Your first question is probably, why use failsafe in addition to the surefire. The quick answer is that the failsafe will continue if your servlets cause an exception allowing you to test all of your files.

Failsafe plugin
We'll get started adding the Failsafe plugin to the POM file. In your pom.xml file, add the following to your section:
<plugin>
    <artifactId>maven-failsafe-plugin</artifactId>
    <version>2.6</version>
    <configuration>
        <includes>
            <include>**/*SysTest.java</include>
        </includes>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>integration-test</goal>
            </goals>
        </execution>
    </executions>
</plugin>

When the goal "integration-test" is run, the this testing plugin will include all files named SysTest.java. This should be changed to your specific package setup. Using "SysTest", "ITCase" or other pre/post fixes and separate packages are other options to segregate your integration test from your unit test.

Application Server
We now have the failsafe plugin, but we have to figure out how to run the application server before the integration test run. This is achieved by running the maven-gae-plugin, yes, the same plugin used to run your webapp. In recent releases of the maven-gae-plugin, developers added the stopPort and stopKey methods which allow failsafe to start and stop the application sever. The plugin can used the phases "pre-integration-test" and the "post-integration-test" to start and stop the GAE server.

Here is sample code to add to your pom.xml file inside of the section (typically right below the failsafe-plugin added above:
<plugin>
    <groupId>net.kindleit</groupId>
    <artifactId>maven-gae-plugin</artifactId>
    <version>0.7.1</version>
    <configuration>
        <scanIntervalSeconds>10</scanIntervalSeconds>
        <stopKey>STOP</stopKey>
        <stopPort>8005</stopPort>
    </configuration>
    <executions>
        <execution>
            <id>start-gae</id>
            <phase>pre-integration-test</phase>
            <goals>
                <goal>start</goal>
            </goals>
            <configuration>
                <scanIntervalSeconds>0</scanIntervalSeconds>
                <daemon>true</daemon>
            </configuration>
        </execution>
        <execution>
            <id>stop-gae</id>
            <phase>post-integration-test</phase>
            <goals>
                <goal>stop</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Notice, we are using the "start" verses using "run" because "start" does not include all of the task to "package" the war allowing the failsafe plugin to stop the GAE server.

Adding HttpUnit
In this posting, we will be adding and using HttpUnit as it's one of the most comprehensive and stable HTML unit testing libraries. HttpUnit not only allows you to query your URLs, but you can unit-test JavaScript, see errors for CSS, JavaScript and malformed HTML. HtmlUnit also allows you to emulate various different browsers including FireFox and Internet Explorer.

The integration test are very similar to the unit test buy using the same testing context file, Spring JUnit helper, Google Local Testing helpers and JUnit.

An integration test using HttpUnit
Here is a very basic example of an integration test:
package com.example.web.controllers;

...imports...

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"/testApplicationContext.xml"})
public class HomeControllerSysTest extends AbstractJUnit4SpringContextTests {

    private static final Logger log = Logger.getLogger(
            HomeControllerSysTest.class.getName());
    private final LocalServiceTestHelper helper =
            new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig());

    @Before
    public void setUp() {
        helper.setUp();
    }

    @After
    public void tearDown() {
        helper.tearDown();
    }

    @Test
    public void testHomeController() throws IOException {
        final String url = "http://localhost:8080/movie/test";

        final WebClient webClient = new WebClient();
        final HtmlPage page = webClient.getPage(url);
        assertEquals("The Page Title", page.getTitleText());

        // there are many different methods to query everything on your
        // page. Please refer to the HttpUnit homepage
        HtmlElement header = page.getElementsByTagName("h1").get(0);
        assertNotNull(header);

        String headerValue = header.getNodeValue();
        assertEquals(headerValue, "Hello World!");
    }
}

How to test
Now, it's time to run the test. There are several methods for running integration test without running the unit test, but those tactics will not be covered in this posting. To run the integration test, type the following:
mvn integration-test

That's it! Between this posting and the previous [Part 5] you should be able to test a large majority of your Java Web Application.

Spring 3 + maven2 + Google App Engine: Part 5 [Unit test]

Unit testing is one of the most important keystones to Agile programming methodologies. Testing with maven and Spring can be a big pain, so here's a quick tutorial on how to set it up. I will not cover proper testing strategies, but I'll give you the ability to setup your own testing.

First, we'll focus on unit testing. Unit testing is easily done using the "surefire plugin". To add the plugin, edit your pom.xml file, add the following inside of the section:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.4</version>
    <configuration>
        <includes>
            <include>**/*Test.java</include>
        </includes>
        <excludes>
            <exclude>**/*SysTest.java</exclude>
        </excludes>
    </configuration>
</plugin>

What this does is to tell Maven that during the phase "test", to run the JUnit test using the include/exclude rules.

Next we need to create a test file to run. Luckily we can use annotations to decorate our test files making it much easier. For this example, I will create a file in the test package called EventServiceTest.java.

package com.example;
...imports...
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"/testApplicationContext.xml"})
public class EventServiceTest extends AbstractJUnit4SpringContextTests {

    @Autowired
    private EventService eventService;

    // Google's helper file to put all data storage in memory
    private final LocalServiceTestHelper helper =
            new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig());

    @Before
    public void setUp() {
        helper.setUp();
    }

    @After
    public void tearDown() {
        helper.tearDown();
    }

    @Test
    public void testCreateEvent() {
        Event event = eventService.getByName("pie day");
        assertNotNull("Pie Day should always exist!", event);
    }
}

There are a few things to notice in this example. The LocalServiceTestHelper is a class provided by Google that will allow data to be stored in memory instead of interferring with your local datastore. There are several different types of configurations and customizations that can be passed into the constructor. Visit http://code.google.com/appengine/docs/java/tools/localunittesting/javadoc/ to check out other options.

If you noticed, we are loading a new context in the @ContextConfiguration annotation. You will need to create a new context file (similar to applicationContext.xml) which usually is a copy of the applicationContext.xml file, with changes to use mock services.

I choose to specify the context file by location, which is set in my POM file to be at /src/test/resources (refer to Part 2 to see full POM file). Another option could be to use classpath scanning.

Lastly, notice that the @RunWith(SpringJUnit4ClassRunner.class), this sets up the test file for class loading.

You can now run your unit test by running the following command:
mvn test

Next post will contain information about how to create and run integration test.