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.

September 10, 2010

Spring 3 + maven2 + Google App Engine: Part 4 [Deployment]

Now you should have an easy application that is working and you'd like to deploy to Google's servers. Let's take a look at the appengine-web.xml file located in the project's /WEB-INF folder. This file contains all of the settings related to the appengine web application.

Here is an example appengine-web.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
    <application>my-unique-app-name</application>
    <!--  This variable is defined in your POM file -->
    <version>${gae.application.version}</version>
    <system-properties>
        <property name="java.util.logging.config.file" value="WEB-INF/classes/logging.properties"/>
    </system-properties>
    <sessions-enabled>true</sessions-enabled>
    <!--
    <static-files>
        <include path="/site/**" expiration="1d 12h" />
    </static-files>
    -->
</appengine-web-app>

Notice that the application node is filled out with your unique Google App Engine application name which was created during the creation of the application.

If you take a look at part 2 of this series which goes more in depth on the POM file, you will notice at the bottom of the file there are several properties/variables set. One of these is the application version, which correspond to the different versions you can deploy to Google's servers. Notice that in the POM file, there are several version setup using different profiles. When you deploy your application, you can setup different version numbers for testing, integration builds, staging or other required environments. The deployed version will follow the following URL format:

For a version of "0":
http://0.latest.your-unique-appengine-name.appspot.com/

For a version of "2":
http://2.latest.your-unique-appengine-name.appspot.com/

I recommend using the "0" version for testing, "1" for staging and "2" for production. This allows you to release to test and stage versions of your application, which will still use the same database, without releasing to production.

Now, onto how to deploy. If you have been following this series, you will be using the GAE plugin for Maven, which comes with many handy goals. Using the "gae:deploy" will deploy your application to the Google servers according to the appengine-web.xml file.

NOTE: I recommend creating batch files to deploy your application. Here is a very basic example of a bash script file:

#!/bin/bash
mvn gae:deploy -Dmaven.test.skip=true -Prelease-build

I have set the profile to "release-build" and decided to skip my test when doing my releases. Typically you will need to provide your email and password on the Google App Engine account to deploy.

Lastly, once your have a successful deployment, follow these directions.

  1. Check your application by using the URL format given above
  2. Once you have verified the version has been uploaded, goto http://www.appspot.com, login and goto your application's main page
  3. Look for "Application Versions" and goto those settings
  4. Select the "2" to be your version to be the "default"

Congratulations, you have the necessary skills to create an application using Google App Engine and Spring 3.0!

Later post will give more insights and tricks that I have learned while developing a full application exclusively hosted on Google App Engine.