One of those seem­ingly triv­ial top­ics of debate amongst soft­ware devel­op­ers which is liable to irk me is the sub­ject of depen­den­cies. There is noth­ing more frus­trat­ing than check­ing out a project only only to dis­cover that it has no end of “exter­nals” and other assorted envi­ron­men­tal depen­den­cies that are out­side of the con­trol of the project itself. Ide­ally, the struc­ture and build sup­port for an exe­cutable project (e.g. a web appli­ca­tion) should make it pos­si­ble for it to be run “out-of-the-box” because three sig­nif­i­cant costs are incurred where this is not the case:

  1. Time-to-start” for any new devel­oper is increased.
  2. The appli­ca­tion becomes more frag­ile through expo­sure to change in exter­nal dependencies.
  3. End-to-end func­tional, in-container test­ing of the appli­ca­tion becomes cor­re­spond­ingly more com­plex, the setup process for which now has to effec­tively doc­u­ment and keep pace with changes to exter­nal sys­tems (which, in prac­tice, are often not known until after your tests start fail­ing, lead­ing to wasted time debug­ging the cause of failure).

The argu­ment I some­times hear against my approach is that it attempts to cre­ate a mono­lithic sys­tem (the “one ring to rule them all” syn­drome) and that sep­a­ra­tion into “mod­ules” helps to cre­ate smaller, more man­age­able appli­ca­tions (smaller: maybe; more man­age­able: def­i­nitely not, in my view). “Mod­u­lar­i­sa­tion” and “service-oriented” approaches need not incur the loss of compile-time safety (which is an advan­tage of a lan­guage like Java) nor the frag­men­ta­tion of inte­gra­tion con­cerns. To demon­strate this, and for my own enjoy­ment and edu­ca­tion, I have recently been work­ing in my spare time on putting together a small web appli­ca­tion that I call “Appo­site”. In this and sub­se­quent posts, I would like to share with you what I see as some of the key struc­tural and archi­tec­tural approaches that I am adopt­ing, begin­ning with the assump­tion that the entire appli­ca­tion must always remain exc­etable as a stand­alone arte­fact using just the build script so that it is pos­si­ble to do both end-to-end func­tional test­ing of the appli­ca­tion as part of the build and to make it pos­si­ble to do “live cod­ing” (where changes in com­piled code are quickly vis­i­ble within a run­ning instance.

The appli­ca­tion itself is a very con­ven­tional Java web appli­ca­tion build using the now-standard stack of Spring and Hiber­nate. For builds, I am using Maven. For test­ing, I am using JUnit (of course), Web­Driver, and JBe­have (whilst arguably not as good as cucum­ber it is eas­ier to use with Java projects and fits more nicely with my “out-of-the-box” non-functional require­ments). As you can see, hardly ground­break­ing stuff … but some­thing I still see done “wrong” in so many places and by so many peo­ple (which is some­what inve­vitable by virtue of its popularity).

Get­ting the basic project struc­ture in place

Let’s begin from the very basics, assum­ing a stan­dard Maven web appli­ca­tion project structure:

  • appo­site
    • pom.xml
    • src
      • main
        • java
        • resources
        • webapp
          • WEB-INF
            • web.xml
      • test
        • java
        • resources

In the pom.xml, we declare the test­ing depen­den­cies we are going to be using, start­ing with JUnit:

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.8.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.jbehave</groupId>
            <artifactId>jbehave-core</artifactId>
            <version>3.3.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-common</artifactId>
            <version>${selenium.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-firefox-driver</artifactId>
            <version>${selenium.version}</version>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>commons-logging</groupId>
                    <artifactId>commons-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-support</artifactId>
            <version>${selenium.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>3.0.5.RELEASE</version>
            <scope>test</scope>
        </dependency>

Func­tional tests are inher­ently slower to run that unit tests and we do not nec­es­sar­ily want to run them all the time. There­fore, we want to exe­cute them only dur­ing Maven’s inte­gra­tion test phase and, even then, only when we spec­ify a func­tional test pro­file. To achieve this, we begin by adding the maven-failsafe-plugin to the build sec­tion of our POM:

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-failsafe-plugin</artifactId>
                <version>2.8</version>
            </plugin>

By default, this will run JUnit tests that match the pat­tern **/*IT.java dur­ing the inte­gra­tion test phase of the build. You can stick with the default, how­ever I pre­fer the slightly more descrip­tive nam­ing con­ven­tion **/*FunctionalTest.java — that can yield slightly over-long test names but it is at least blind­ingly clear what sort of test your test class is! To ensure my pre­ferred test nam­ing con­ven­tion does not con­flict with the stan­dard sure­fire plu­gin defaults, I con­fig­ure excludes and includes in the surefire-plugin:

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

By default in Maven, the webapp source folder is not on the test class­path. It makes this kind of in-container func­tional test­ing much eas­ier if it is. To place the webapp folder on the class­path, you can use the build-helper-maven-plugin:

            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>build-helper-maven-plugin</artifactId>
                <version>1.5</version>
                <executions>
                    <execution>
                        <id>add-test-resource</id>
                        <phase>generate-test-sources</phase>
                        <goals>
                            <goal>add-test-resource</goal>
                        </goals>
                        <configuration>
                            <resources>
                                <resource>
                                    <directory>src/main/webapp</directory>
                                </resource>
                            </resources>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

Next in the build plu­g­ins we need to declare and con­fig­ure the jetty-maven-plugin so that we can fire-up the entire web appli­ca­tion using mvn jetty:run:

            <!-- You need to specify -Djetty.port=${port} if 8080 is already bound on the build machine -->
            <plugin>
                <groupId>org.mortbay.jetty</groupId>
                <artifactId>jetty-maven-plugin</artifactId>
                <version>7.2.0.v20101020</version>
                <dependencies>
                    <!-- you can declare what would commonly be your container-provided dependencies here, such as log4j etc -->
                </dependencies>
                <configuration>
                    <webAppConfig>
                        <contextPath>/${project.artifactId}</contextPath>
                    </webAppConfig>
                    <jettyConfig>src/test/resources/jetty.xml</jettyConfig>
                    <useTestClasspath>true</useTestClasspath>
                    <scanIntervalSeconds>10</scanIntervalSeconds>
                    <stopKey>${project.artifactId}-stop</stopKey>
                    <stopPort>9999</stopPort>
                </configuration>
            </plugin>

Note the use of the jetty.xml jet­ty­Con­fig there: I use this to declare a JNDI data­source (this needs to be a jetty server con­fig — using a jetty web appli­ca­tion con­fig will result in hor­rific mem­ory leaks around data­base con­nec­tions with the reg­u­lar restarts that you may well want if you are doing “live cod­ing” against a run­ning jetty instance using this build con­fig) so that all my app has to know is the JNDI name and the actual details of this will always be encap­su­lated within the con­tainer (in this case, the test har­ness). In Appo­site, I am using an in-memory HSQLDB data­base for test­ing, so I declare c3p0 and HSQLDB as container-provided depen­den­cies of the jetty plu­gin and my jetty.xml looks like this:

<?xml version="1.0"  encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://jetty.mortbay.org/configure.dtd">
<Configure class="org.eclipse.jetty.server.Server">
    <New class="org.eclipse.jetty.plus.jndi.Resource">
        <Arg>jdbc/apposite</Arg>
        <Arg>
            <New class="com.mchange.v2.c3p0.ComboPooledDataSource">
                <Set name="driverClass">org.hsqldb.jdbcDriver</Set>
                <Set name="jdbcUrl">jdbc:hsqldb:mem:apposite</Set>
                <Set name="user">sa</Set>
                <Set name="password"></Set>
            </New>
        </Arg>
    </New>
</Configure>

The final touch in the POM is to setup a func­tional tests pro­file so that we can exe­cute the in-container tests using mvn test -PFunctionalTests (or -PWhateverYouCallYourProfile):

        <profile>
            <id>FunctionalTests</id>
            <activation>
                <activeByDefault>false</activeByDefault>
            </activation>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-failsafe-plugin</artifactId>
                        <configuration>
                            <includes>
                                <include>**/*FunctionalTest.java</include>
                            </includes>
                        </configuration>
                        <executions>
                            <execution>
                                <id>integration-test</id>
                                <goals>
                                    <goal>integration-test</goal>
                                </goals>
                            </execution>
                            <execution>
                                <id>verify</id>
                                <goals>
                                    <goal>verify</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                    <plugin>
                        <groupId>org.mortbay.jetty</groupId>
                        <artifactId>jetty-maven-plugin</artifactId>
                        <executions>
                            <execution>
                                <id>start-jetty</id>
                                <phase>pre-integration-test</phase>
                                <goals>
                                    <goal>run</goal>
                                </goals>
                                <configuration>
                                    <scanIntervalSeconds>0</scanIntervalSeconds>
                                    <daemon>true</daemon>
                                </configuration>
                            </execution>
                            <execution>
                                <id>stop-jetty</id>
                                <phase>post-integration-test</phase>
                                <goals>
                                    <goal>stop</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
        </profile>

In this pro­file, we tell the jetty plu­gin to fire up our webapp just before the inte­gra­tion test phase starts (and to stop it once it is com­plete) and inform the fail­safe plu­gin that it should exe­cute tests that match the nam­ing con­ven­tion **/*FunctionalTest.java (you could, of course, avoid hav­ing to have this bit of con­fig­u­ra­tion by sim­ply accept­ing the default **/*IT.java con­ven­tion … but I have some­thing of a dis­like of “pub­lic abbre­vi­a­tions” like this). Note also that the scanIntervalSeconds prop­erty is set to 0: we do not want jetty acci­den­tally detect­ing some change on the class­path due to code gen­er­at­ing some resource there and restart­ing mid-test as a con­se­quence. Set­ting this prop­erty to 0 ensures this by turn­ing off the jetty plu­gin change scan. This over­rides the set­ting in the build plu­gin con­fig­u­ra­tion (where we set it to 10 sec­onds) which was intended to have the oppo­site effect: when we do “live cod­ing” against a run­ning jetty instance, we want it to pick-up and deploy our code changes regularly.

As usual with Maven, there is a bit of an excess of pointy brack­ets here … but once you have a use­ful project struc­ture you can always turn it into an arche­type, thus avoid­ing the need to have to recre­ate it by hand every time.

Cre­at­ing a “frame­work” for the func­tional tests

We now have a web appli­ca­tion (albeit with no actual code) that we can fire up and run from our build script and which will auto­mat­i­cally run cer­tains tests in-container dur­ing the build if and when we so choose. Before we plough ahead, we should stop and give a lit­tle thought to how we want to divide up our func­tional tests and what kind of sup­port infra­struc­ture they might ben­e­fit from.

The first thought that occurred to me at this point is “I’m using Spring already so surely I must be able to re-utilise it to make man­ag­ing my test code eas­ier?” This is indeed pos­si­ble but, if you are using annotation-based Spring con­text con­fig­u­ra­tion (as I am), then it is a very good idea to use a com­pletely sep­a­rate name­space for your func­tional test “con­text” to ensure there is no chance of it becom­ing mixed up with your real appli­ca­tion. In the case of Appo­site, my appli­ca­tion name­space is org.apposite. There­fore, rather than use org.apposite.bdd (or sim­i­lar), I opted for bdd.org.apposite: no chance of a con­flict, as it is not a sub­set of the appli­ca­tion name­space. I began by mak­ing a min­i­mal appli­ca­tion con­text con­fig that would pro­vide pretty much all the sup­port­ing infra­struc­ture I would need for my tests:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <context:property-placeholder location="classpath:application.properties,classpath:environment.properties" system-properties-mode="OVERRIDE" />

    <context:component-scan base-package="bdd.org.apposite" />

    <bean id="web" class="org.openqa.selenium.firefox.FirefoxDriver" scope="prototype" destroy-method="close" />

</beans>

This file achieves the following:

  1. Pro­vides access to “appli­ca­tion” and “envi­ron­ment” prop­er­ties (of the real appli­ca­tion) from src/main/resources/application.properties and src/test/resources/environment.properties, respec­tively, so that these val­ues can be utilised to con­struct test cases and asser­tions. Note that the environment.properties defines prop­er­ties spe­cific to the con­tainer or the envi­ron­ment and would nor­mally be pro­vided by the tar­get deploy­ment con­tainer. The ver­sion in src/test/resources there­fore repli­cates this require­ment for the test con­tainer whilst also serv­ing as a form of doc­u­men­ta­tion, thus help­ing me to keep these two dis­tinct areas of con­fig­u­ra­tion entirely seper­ate whilst main­tain­ing runnabil­ity “out-of-the-box”.
  2. Instan­ti­ates anno­tated com­po­nents within the test name­space only via context:component-scan.
  3. Pro­vides access to Web­Driver (I’m using the fire­fox dri­ver here). Notice that the scope is prototype so that each test will get it’s own new instance. Here I also spec­ify a destroy-method — that is a bit of “belt & braces” para­noia to try and ensure that we do not leave Fire­fox win­dows open on com­ple­tion of a test case (it doesn’t actu­ally achieve that due to the nature of the Spring bean life­cy­cle in rela­tion to the test exe­cu­tion, but out of pure super­sti­tion I felt it bet­ter defined than not, if you know what I mean).

Next, I started think­ing about how I wanted to divide up my tests. This is worth doing if you are using some­thing like JBe­have because one of the first things you will need to do is to tell it how to load “story files”. Con­se­quently, know­ing which story files to load is impor­tant. If you sim­ply load all your story files in one go (which is an option) you will have one mas­sive func­tional test. That may not nec­es­sar­ily be a prob­lem, but it does limit things some­what, espe­cially in terms of report­ing, and may quickly become unwieldy for any­thing but the small­est and most sim­ple of appli­ca­tions. In line with gen­eral BDD guide­lines, I wanted to split my tests up into “func­tional areas” within which one or more user sce­nar­ios could be encap­su­lated (for exam­ple, “reg­is­tra­tion”). I opted to go for a one-to-one rela­tion between a func­tional test class and story file because then, once a basic test exe­cu­tion mech­a­nism was in place, I would sim­ply be able to write the story file and cre­ate a sim­ple test class that spec­i­fied the story file to run. I would then see these as indi­vid­u­ally exe­cuted tests within both the Maven inte­gra­tion test phase and in the JBe­have report­ing. I felt the cost of hav­ing to pro­duce at least one “no code” class per story file was off­set by the flex­i­bil­ity this approach would pro­vide. After writ­ing a few tests, I extracted the fol­low­ing through a process of refactoring:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:bdd/org/apposite/functional-tests.xml")
public abstract class AbstractFunctionalTest extends Embedder {

    @Autowired private GenericApplicationContext ctx;

    private final String includes;

    protected AbstractFunctionalTest(String storyFile) {
        includes = "bdd/org/apposite/" + storyFile;
    }

    @Test public void runStories() {
        useCandidateSteps(new InstanceStepsFactory(configuration().useStoryReporterBuilder(new StoryReporterBuilder().withCodeLocation(codeLocationFromClass(getClass())).withFormats(CONSOLE, TXT, XML, HTML)), ctx.getBeansWithAnnotation(Steps.class).values().toArray()).createCandidateSteps());
        runStoriesAsPaths(new StoryFinder().findPaths(codeLocationFromClass(getClass()), includes, ""));
    }

}

This class extends org.jbehave.core.embedder.Embedder, which is the key thing in enabling it to become an exe­cutable JUnit test that runs JBe­have sto­ries. (We also have to over­ride some of JBehave’s frankly ter­ri­ble report­ing defaults!) It utilises the Spring test­ing frame­work and the test con­text described above to allow autowiring of JBe­have “steps” (which are just POJOs anno­ta­tion with @Given, @When, and @Then meth­ods that are matched against the cor­re­spond­ing lines from a story file. I cre­ated a cus­tom Spring com­po­nent stereo­type called @Steps:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface Steps {
    String value() default "";
}

All Spring beans anno­tated with the @Steps anno­ta­tion are pre­sented to JBe­have as can­di­date steps for the exe­cu­tion of a story file which is spec­i­fied by a con­crete sub­class. At present, there is no clever fil­ter­ing of can­di­date steps and all story files are assumed at the very least to live within the bdd.org.apposite name­space. You could prob­a­bly be much more sophis­ti­cated if nec­es­sary, but this has proved suf­fi­cient for my require­ments so far.

Writ­ing the tests

Now we are in a posi­tion to write a basic story file. Let’s start with a “secu­rity” fea­ture, for exam­ple: “I should not be able to access the admin­is­tra­tion dash­board unless I am logged in and have suf­fi­cient priv­i­leges”. Whilst per­haps not the best exam­ple of a “func­tional area” (because “secu­rity” is in real­ity an con­stituent com­po­nent of other func­tional areas and cuts across them) it will nonethe­less suf­fice for now:

Scenario: I cannot access the administration dashboard unless I am logged in

Given I am not logged in
When I go to the administration dashboard
Then I am asked to login
Then I enter the administrator credentials
Then I am redirected to the administration dashboard

This is an extremely basic exam­ple of a story file. For a full descrip­tion of all the pos­si­ble fea­tures of story files in JBe­have, check out their doc­u­men­ta­tion (which, whilst it appears com­pre­hen­sive, does not win any prizes for clar­ity). Noneth­less, I’m sure you get the gist: each sce­nario con­sists of some pre­con­di­tions, an oper­a­tion, and some post­con­di­tions. You can have as many sce­nar­ios per “story file” as you like, sep­a­rated by scenario dec­la­ra­tions (this is why I opted to use the *.stories exten­sion rather than the more com­mon *.story — the for­mer made more gram­mat­i­cal sense to me).

Next, because I opted for a one-to-one rela­tion between story files and exe­cutable tests, you need to cre­ate a very sim­ple class that will indi­cate that this story file needs to be run:

public class SecurityFunctionalTest extends AbstractFunctionalTest {

    public SecurityFunctionalTest() {
        super("security.stories");
    }

}

Bingo! We have a behaviour-driven func­tional test. “But where the hell is all the logic?” I am sure you are ask­ing (if you are, it is, of course, the right ques­tion … and I shall move onto that now).

Organ­is­ing respon­si­bil­i­ties into page objects

The good peo­ple behind Web­Driver (and almost any­one else who has done this kind of test­ing) rightly rec­om­mend that you take the impor­tant step of describ­ing your web appli­ca­tion in terms of “page objects”. A page object should, loosely speak­ing, cor­re­spond to the response from a given URI and encap­su­late the “ser­vices” (forms, links, key infor­ma­tion) that it pro­vides. In the test case above, I am inter­ested in two pages:

  1. A “logout” page (this is the least obvi­ous but bear in mind that we need to encap­su­late access to a URI that will ensure that we are not logged in to com­plete the first step)
  2. The “admin­is­tra­tion dash­board” page
  3. The “login” page

Clearly, there are going to be many things that are com­mon to all pages (even if you have a very inco­her­ent user inter­face). For exam­ple, at the very least, you should be able to “visit” all pages. Also, for test­ing pur­poses, we should be able to assert what page we are cur­rently on. There­fore, I will cut to the chase and begin with an abstract super­class for them all that can be used to describe these com­mon features:

public abstract class AbstractPage {

    private static final int DEFAULT_PORT = 8080;

    private String url;

    private WebDriver web;

    protected AbstractPage(String uri, WebDriver web) {
        this.web = web;
        int port = System.getProperty("jetty.port") != null ? Integer.valueOf(System.getProperty("jetty.port")) : DEFAULT_PORT;
        url = "http://localhost:" + port + "/apposite" + uri;
    }

    public void assertIsCurrentPage() {
        assertThat(isCurrentPage(), is(true));
    }

    public abstract boolean isCurrentPage();

    public final void visit() {
        web.get(url);
    }

There is a bug in maven-surefire-plugin < 2.6 which will pre­vent the sys­tem prop­erty being avail­able to the test here, so will have to hard-code the port on which you run your func­tional tests or upgrade. See SUREFIRE-121.

Our abstract page is respon­si­ble for man­ag­ing the Web­Driver instance (which it requires for instan­ti­a­tion), and coor­di­nat­ing what port, host, and con­text path we are run­ning on (the lat­ter two hard­coded here for the sake of sim­plic­ity but eas­ily exter­nal­is­able through sys­tem prop­er­ties if nec­es­sary). This means that con­crete page instances only need to spec­ify what URI they have (with­out hav­ing to con­sider con­text paths etc) and the abstract super­class can per­form all the actual “get­ting” and so forth.

Next, we define our actual, con­crete pages:

public class AdministrationDashboardPage extends AbstractPage {

    @FindBy(css = "h2") private WebElement heading;

    public AdministrationDashboardPage(WebDriver web) {
        super("/admin", web);
    }

    @Override public boolean isCurrentPage() {
        return "Administration Dashboard".equals(heading.getText());
    }

}

This first page def­i­n­i­tion is extremely sim­ple. Using the “finder” anno­ta­tions pro­vided by the selenium-support arte­fact, we use the pres­ence of some head­ing text to deter­mine whether or not we are actu­ally on the admin dash­board page. I will come back to the @FindBy anno­ta­tion in a lit­tle while.

public class LogoutPage extends AbstractPage {

    public LogoutPage(WebDriver web) {
        super("/users/logout", web);
    }

    @Override public boolean isCurrentPage() {
        return false;
    }

}

The logout page is even more sim­ple because we should only ever need to “visit” it and we should never actu­ally be “on it”. It is just a URI.

public class LoginPage extends AbstractPage {

    @FindBy(css = "#j_username") private WebElement username;

    @FindBy(css = "#j_password") private WebElement password;

    public LoginPage(WebDriver web) {
        super("/users/login", web);
    }

    public void enterUsername(String username) {
        this.username.sendKeys(username);
    }

    public void enterPassword(String password) {
        this.password.sendKeys(password);
    }

    public void login() {
        password.submit();
    }

    @Override public boolean isCurrentPage() {
        return username != null && password != null;
    }

}

The login page shows a lit­tle more about how page objects are intended to be used in that it encap­su­lates access to crit­i­cal form ele­ments (again, injected using the Web­Driver anno­ta­tions) in what can be viewed as “ser­vice meth­ods”. This means that any change in the page should only ever require an update to one code loca­tion. How­ever, it does also place con­sid­er­able impor­tance on being able to accu­rately iden­tify what “a page” really is in your appli­ca­tion (this becomes more com­plex when you are deal­ing with asyn­chro­nous JavaScript mak­ing calls to HTTP “ser­vices” from within a supposed-page — these ser­vices are, in effect, pages them­selves even if the human end-user never sees them in-the-raw — so keep an open mind about the def­i­n­i­tion of a page there!)

Ora­gan­is­ing logic into steps objects

Finally, we need to cre­ate imple­men­ta­tions for the “steps” which are detailed in our “story file” (one of the nice fea­tures of JBe­have is that you can tell it not to fail tests where imple­men­ta­tions have not yet been done — mark­ing these as “pend­ing” in the reports — this way one bunch of peo­ple can get busy writ­ing sto­ries which do not have to be com­mit­ted at the same time as the imple­men­ta­tions, open­ing the pos­si­bil­ity of these two tasks being com­pleted by sep­a­rate groups; e.g. “busi­ness ana­lysts” on the one hand and soft­ware devel­op­ers on the other).

Again, there are cer­tainly going to be a num­ber of com­mons aspects to these steps objects, so you can begin with an abstract class. Mine cur­rently looks like this:

@Scope(BeanDefinition.SCOPE_PROTOTYPE) public abstract class AbstractSteps {

    protected final WebDriver web;

    protected AbstractSteps(WebDriver web) {
        this.web = web;
    }

    @AfterScenario public void closeWebDriver() {
        web.close();
    }

    protected final <T extends AbstractPage> T initPage(Class<T> pageClass) {
        return PageFactory.initElements(web, pageClass);
    }

}

Notice that, because my steps are going to be man­aged by the functional-tests.xml Spring con­text, I can define them as pro­to­type using @Scope so that we get a fresh instance wher­ever it is required with a cor­re­spond­ingly fresh instance of the pro­to­type Web­Driver bean, which is autowired in. I define only one com­mon method at present, which pro­vides a strongly-typed means to instan­ti­ate a Web­Driver page object (to use the @FindBy Web­Driver anno­ta­tions, you have to instan­ti­ate the page using the PageFactory.initElements(WebDriver, Object) method). Addi­tion­ally, I make absolutely sure that the fire­fox win­dow gets closed by using a JBe­have @AfterScenario method to close it (this is almost directly equiv­a­lent to JUnit’s @After anno­ta­tion). Now I am in a good posi­tion to start writ­ing steps classes that shouldn’t need to worry about too much extra­ne­ous fluff. Let’s take a look at the SecuritySteps that I wrote to imple­ment my basic story file described above:

@Steps public class SecuritySteps extends AbstractSteps {

    private String username;

    private String password;

    @Autowired public SecuritySteps(WebDriver web) {
        super(web);
    }

    @Given("I am not logged in")
    public void iAmNotLoggedIn() {
        initPage(LogoutPage.class).visit();
    }

    @When("I go to the administration dashboard")
    public void iGoToTheAdministrationDashboard() {
        initPage(AdministrationDashboardPage.class).visit();
    }

    @Then("I am asked to login")
    public void iAmAskedToLogin() {
        initPage(LoginPage.class).assertIsCurrentPage();
    }

    @Then("I enter the administrator credentials")
    public void iEnterTheAdministratorCredentials() {
        LoginPage p = initPage(LoginPage.class);
        p.enterUsername(username);
        p.enterPassword(password);
        p.login();
    }

    @Then("I am redirected to the administration dashboard")
    public void iAmRedirectedToTheAdministrationDashboard() {
        initPage(AdministrationDashboardPage.class).assertIsCurrentPage();
    }

    @Autowired public void setUsername(@Value("${apposite.security.root.user.name}") String username) {
        this.username = username;
    }

    @Autowired public void setPassword(@Value("${apposite.security.root.user.password}") String password) {
        this.password = password;
    }

}

Some things to notice about this class:

  1. It is anno­tated with my cus­tom @Steps anno­ta­tion so that the AbstractFunctionalTest will pick it up from the appli­ca­tion con­text and present it as can­di­date steps for the story file.
  2. Each method (exclud­ing the set­ters) cor­re­sponds to one of the state­ments from the story file: they are matched on the anno­ta­tion text. There are far more funky things one can do in terms of para­me­ter injec­tion here — this is just the most basic exam­ple possible.
  3. Because I decided to use Spring to man­age my steps classes, I am able not only to autowire any Web­Driver instance I choose to use, but also con­fig­u­ra­tion val­ues from the appli­ca­tion or envi­ron­ment prop­er­ties files (using @Value annotations)

Con­clu­sion

All of this is might seem like quite a lot of code to achieve the sim­ple goal of run­ning in-container func­tional tests. Nat­u­rally, there are ways of doing it with less code. How­ever, most of the above is sim­ply infra­struc­ture which can be extracted and shared between mul­ti­ple projects and is intended to pro­vide a har­ness that min­imises the amount of work required to add test cases, which, being the most numer­ous “code type” should ide­ally require the least amount of actual code indi­vid­u­ally. Both steps and page objects might fea­si­bly con­sid­ered as a form of “shared library”: re-use should most def­i­nitely be a goal and, once you begin to get a more com­pre­hen­sive col­lec­tion, you should not be required to always write new page objects or step imple­men­ta­tions in order to write new test cases (but don’t move them out of the project until you absolutely have to!)

Con­se­quently, with a lit­tle up-front work and thought, it is pos­si­ble to reduce the costs of adding test cases over time. This is often cited as a goal. Sadly, it is too often the case in prac­tice that a some­what lack­adaisi­cal approach at the out­set leads to this aim being con­founded: test har­nesses become brit­tle, test code becomes more com­plex than the code it tests and has bugs, tests start to blink, the whole thing becomes dis­or­gan­ised and confused.

My aim through­out has been to bal­ance flex­i­bil­ity with reg­u­lar­ity and pre­dictabil­ity: make the con­ven­tions clear and repeat­able but not too rigid. From the per­spec­tive of the build cycle, one of the keys to achiev­ing that pre­dictabil­ity is mak­ing sure that devel­op­ers can always run these kinds of test when­ever they need to (reduc­ing the time to feed­back). Hav­ing a build infra­struc­ture such as the one described here helps devel­op­ers to avoid not only “break­ing the build” but also “break­ing the functionality”.

Clearly, there is code to write in order to actu­ally make this test pass — which I shall hope­fully come on to in sub­se­quent posts — but that is one of the really nice things about BDD and func­tional test­ing of this kind: if you are using an agile method­ol­ogy (which you should be), then your JBe­have sto­ries, whilst not absolutely equiv­a­lent, should bear a close rela­tion to your iter­a­tion story cards. From this per­spec­tive, they can serve as a “def­i­n­i­tion of done” for any given ver­ti­cal slice of func­tion­al­ity: you can pro­ceed using TDD at a unit test level — the tests will fail & fail & fail — but, then, once all the var­i­ous units are com­plete, your end-to-end test will pass and you know you are done … or, I should say, you know you are “good enough” because you shouldn’t for­get to do a lit­tle refac­tor­ing and code-tidying before you con­sider it truly complete.