On May 16, 2011 wrote: End-to-End Testing With Maven, Jetty & JBehave
One of those seemingly trivial topics of debate amongst software developers which is liable to irk me is the subject of dependencies. There is nothing more frustrating than checking out a project only only to discover that it has no end of “externals” and other assorted environmental dependencies that are outside of the control of the project itself. Ideally, the structure and build support for an executable project (e.g. a web application) should make it possible for it to be run “out-of-the-box” because three significant costs are incurred where this is not the case:
- “Time-to-start” for any new developer is increased.
- The application becomes more fragile through exposure to change in external dependencies.
- End-to-end functional, in-container testing of the application becomes correspondingly more complex, the setup process for which now has to effectively document and keep pace with changes to external systems (which, in practice, are often not known until after your tests start failing, leading to wasted time debugging the cause of failure).
The argument I sometimes hear against my approach is that it attempts to create a monolithic system (the “one ring to rule them all” syndrome) and that separation into “modules” helps to create smaller, more manageable applications (smaller: maybe; more manageable: definitely not, in my view). “Modularisation” and “service-oriented” approaches need not incur the loss of compile-time safety (which is an advantage of a language like Java) nor the fragmentation of integration concerns. To demonstrate this, and for my own enjoyment and education, I have recently been working in my spare time on putting together a small web application that I call “Apposite”. In this and subsequent posts, I would like to share with you what I see as some of the key structural and architectural approaches that I am adopting, beginning with the assumption that the entire application must always remain excetable as a standalone artefact using just the build script so that it is possible to do both end-to-end functional testing of the application as part of the build and to make it possible to do “live coding” (where changes in compiled code are quickly visible within a running instance.
The application itself is a very conventional Java web application build using the now-standard stack of Spring and Hibernate. For builds, I am using Maven. For testing, I am using JUnit (of course), WebDriver, and JBehave (whilst arguably not as good as cucumber it is easier to use with Java projects and fits more nicely with my “out-of-the-box” non-functional requirements). As you can see, hardly groundbreaking stuff … but something I still see done “wrong” in so many places and by so many people (which is somewhat invevitable by virtue of its popularity).
Getting the basic project structure in place
Let’s begin from the very basics, assuming a standard Maven web application project structure:
- apposite
- pom.xml
- src
- main
- java
- resources
- webapp
- WEB-INF
- web.xml
- WEB-INF
- test
- java
- resources
- main
In the pom.xml, we declare the testing dependencies we are going to be using, starting 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>
Functional tests are inherently slower to run that unit tests and we do not necessarily want to run them all the time. Therefore, we want to execute them only during Maven’s integration test phase and, even then, only when we specify a functional test profile. To achieve this, we begin by adding the maven-failsafe-plugin to the build section 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 pattern **/*IT.java during the integration test phase of the build. You can stick with the default, however I prefer the slightly more descriptive naming convention **/*FunctionalTest.java — that can yield slightly over-long test names but it is at least blindingly clear what sort of test your test class is! To ensure my preferred test naming convention does not conflict with the standard surefire plugin defaults, I configure 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 classpath. It makes this kind of in-container functional testing much easier if it is. To place the webapp folder on the classpath, 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 plugins we need to declare and configure the jetty-maven-plugin so that we can fire-up the entire web application 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 jettyConfig there: I use this to declare a JNDI datasource (this needs to be a jetty server config — using a jetty web application config will result in horrific memory leaks around database connections with the regular restarts that you may well want if you are doing “live coding” against a running jetty instance using this build config) so that all my app has to know is the JNDI name and the actual details of this will always be encapsulated within the container (in this case, the test harness). In Apposite, I am using an in-memory HSQLDB database for testing, so I declare c3p0 and HSQLDB as container-provided dependencies of the jetty plugin 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 functional tests profile so that we can execute 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 profile, we tell the jetty plugin to fire up our webapp just before the integration test phase starts (and to stop it once it is complete) and inform the failsafe plugin that it should execute tests that match the naming convention **/*FunctionalTest.java (you could, of course, avoid having to have this bit of configuration by simply accepting the default **/*IT.java convention … but I have something of a dislike of “public abbreviations” like this). Note also that the scanIntervalSeconds property is set to 0: we do not want jetty accidentally detecting some change on the classpath due to code generating some resource there and restarting mid-test as a consequence. Setting this property to 0 ensures this by turning off the jetty plugin change scan. This overrides the setting in the build plugin configuration (where we set it to 10 seconds) which was intended to have the opposite effect: when we do “live coding” against a running 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 brackets here … but once you have a useful project structure you can always turn it into an archetype, thus avoiding the need to have to recreate it by hand every time.
Creating a “framework” for the functional tests
We now have a web application (albeit with no actual code) that we can fire up and run from our build script and which will automatically run certains tests in-container during the build if and when we so choose. Before we plough ahead, we should stop and give a little thought to how we want to divide up our functional tests and what kind of support infrastructure they might benefit 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 managing my test code easier?” This is indeed possible but, if you are using annotation-based Spring context configuration (as I am), then it is a very good idea to use a completely separate namespace for your functional test “context” to ensure there is no chance of it becoming mixed up with your real application. In the case of Apposite, my application namespace is org.apposite. Therefore, rather than use org.apposite.bdd (or similar), I opted for bdd.org.apposite: no chance of a conflict, as it is not a subset of the application namespace. I began by making a minimal application context config that would provide pretty much all the supporting infrastructure 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:
- Provides access to “application” and “environment” properties (of the real application) from
src/main/resources/application.propertiesandsrc/test/resources/environment.properties, respectively, so that these values can be utilised to construct test cases and assertions. Note that theenvironment.propertiesdefines properties specific to the container or the environment and would normally be provided by the target deployment container. The version insrc/test/resourcestherefore replicates this requirement for the test container whilst also serving as a form of documentation, thus helping me to keep these two distinct areas of configuration entirely seperate whilst maintaining runnability “out-of-the-box”. - Instantiates annotated components within the test namespace only via
context:component-scan. - Provides access to WebDriver (I’m using the firefox driver here). Notice that the scope is
prototypeso that each test will get it’s own new instance. Here I also specify adestroy-method— that is a bit of “belt & braces” paranoia to try and ensure that we do not leave Firefox windows open on completion of a test case (it doesn’t actually achieve that due to the nature of the Spring bean lifecycle in relation to the test execution, but out of pure superstition I felt it better defined than not, if you know what I mean).
Next, I started thinking about how I wanted to divide up my tests. This is worth doing if you are using something like JBehave because one of the first things you will need to do is to tell it how to load “story files”. Consequently, knowing which story files to load is important. If you simply load all your story files in one go (which is an option) you will have one massive functional test. That may not necessarily be a problem, but it does limit things somewhat, especially in terms of reporting, and may quickly become unwieldy for anything but the smallest and most simple of applications. In line with general BDD guidelines, I wanted to split my tests up into “functional areas” within which one or more user scenarios could be encapsulated (for example, “registration”). I opted to go for a one-to-one relation between a functional test class and story file because then, once a basic test execution mechanism was in place, I would simply be able to write the story file and create a simple test class that specified the story file to run. I would then see these as individually executed tests within both the Maven integration test phase and in the JBehave reporting. I felt the cost of having to produce at least one “no code” class per story file was offset by the flexibility this approach would provide. After writing a few tests, I extracted the following 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 executable JUnit test that runs JBehave stories. (We also have to override some of JBehave’s frankly terrible reporting defaults!) It utilises the Spring testing framework and the test context described above to allow autowiring of JBehave “steps” (which are just POJOs annotation with @Given, @When, and @Then methods that are matched against the corresponding lines from a story file. I created a custom Spring component stereotype called @Steps:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface Steps {
String value() default "";
}
All Spring beans annotated with the @Steps annotation are presented to JBehave as candidate steps for the execution of a story file which is specified by a concrete subclass. At present, there is no clever filtering of candidate steps and all story files are assumed at the very least to live within the bdd.org.apposite namespace. You could probably be much more sophisticated if necessary, but this has proved sufficient for my requirements so far.
Writing the tests
Now we are in a position to write a basic story file. Let’s start with a “security” feature, for example: “I should not be able to access the administration dashboard unless I am logged in and have sufficient privileges”. Whilst perhaps not the best example of a “functional area” (because “security” is in reality an constituent component of other functional areas and cuts across them) it will nonetheless suffice 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 example of a story file. For a full description of all the possible features of story files in JBehave, check out their documentation (which, whilst it appears comprehensive, does not win any prizes for clarity). Nonethless, I’m sure you get the gist: each scenario consists of some preconditions, an operation, and some postconditions. You can have as many scenarios per “story file” as you like, separated by scenario declarations (this is why I opted to use the *.stories extension rather than the more common *.story — the former made more grammatical sense to me).
Next, because I opted for a one-to-one relation between story files and executable tests, you need to create a very simple class that will indicate that this story file needs to be run:
public class SecurityFunctionalTest extends AbstractFunctionalTest {
public SecurityFunctionalTest() {
super("security.stories");
}
}
Bingo! We have a behaviour-driven functional test. “But where the hell is all the logic?” I am sure you are asking (if you are, it is, of course, the right question … and I shall move onto that now).
Organising responsibilities into page objects
The good people behind WebDriver (and almost anyone else who has done this kind of testing) rightly recommend that you take the important step of describing your web application in terms of “page objects”. A page object should, loosely speaking, correspond to the response from a given URI and encapsulate the “services” (forms, links, key information) that it provides. In the test case above, I am interested in two pages:
- A “logout” page (this is the least obvious but bear in mind that we need to encapsulate access to a URI that will ensure that we are not logged in to complete the first step)
- The “administration dashboard” page
- The “login” page
Clearly, there are going to be many things that are common to all pages (even if you have a very incoherent user interface). For example, at the very least, you should be able to “visit” all pages. Also, for testing purposes, we should be able to assert what page we are currently on. Therefore, I will cut to the chase and begin with an abstract superclass for them all that can be used to describe these common 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 prevent the system property being available to the test here, so will have to hard-code the port on which you run your functional tests or upgrade. See SUREFIRE-121.
Our abstract page is responsible for managing the WebDriver instance (which it requires for instantiation), and coordinating what port, host, and context path we are running on (the latter two hardcoded here for the sake of simplicity but easily externalisable through system properties if necessary). This means that concrete page instances only need to specify what URI they have (without having to consider context paths etc) and the abstract superclass can perform all the actual “getting” and so forth.
Next, we define our actual, concrete 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 definition is extremely simple. Using the “finder” annotations provided by the selenium-support artefact, we use the presence of some heading text to determine whether or not we are actually on the admin dashboard page. I will come back to the @FindBy annotation in a little 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 simple because we should only ever need to “visit” it and we should never actually 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 little more about how page objects are intended to be used in that it encapsulates access to critical form elements (again, injected using the WebDriver annotations) in what can be viewed as “service methods”. This means that any change in the page should only ever require an update to one code location. However, it does also place considerable importance on being able to accurately identify what “a page” really is in your application (this becomes more complex when you are dealing with asynchronous JavaScript making calls to HTTP “services” from within a supposed-page — these services are, in effect, pages themselves even if the human end-user never sees them in-the-raw — so keep an open mind about the definition of a page there!)
Oraganising logic into steps objects
Finally, we need to create implementations for the “steps” which are detailed in our “story file” (one of the nice features of JBehave is that you can tell it not to fail tests where implementations have not yet been done — marking these as “pending” in the reports — this way one bunch of people can get busy writing stories which do not have to be committed at the same time as the implementations, opening the possibility of these two tasks being completed by separate groups; e.g. “business analysts” on the one hand and software developers on the other).
Again, there are certainly going to be a number of commons aspects to these steps objects, so you can begin with an abstract class. Mine currently 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 managed by the functional-tests.xml Spring context, I can define them as prototype using @Scope so that we get a fresh instance wherever it is required with a correspondingly fresh instance of the prototype WebDriver bean, which is autowired in. I define only one common method at present, which provides a strongly-typed means to instantiate a WebDriver page object (to use the @FindBy WebDriver annotations, you have to instantiate the page using the PageFactory.initElements(WebDriver, Object) method). Additionally, I make absolutely sure that the firefox window gets closed by using a JBehave @AfterScenario method to close it (this is almost directly equivalent to JUnit’s @After annotation). Now I am in a good position to start writing steps classes that shouldn’t need to worry about too much extraneous fluff. Let’s take a look at the SecuritySteps that I wrote to implement 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:
- It is annotated with my custom
@Stepsannotation so that theAbstractFunctionalTestwill pick it up from the application context and present it as candidate steps for the story file. - Each method (excluding the setters) corresponds to one of the statements from the story file: they are matched on the annotation text. There are far more funky things one can do in terms of parameter injection here — this is just the most basic example possible.
- Because I decided to use Spring to manage my steps classes, I am able not only to autowire any WebDriver instance I choose to use, but also configuration values from the application or environment properties files (using
@Valueannotations)
Conclusion
All of this is might seem like quite a lot of code to achieve the simple goal of running in-container functional tests. Naturally, there are ways of doing it with less code. However, most of the above is simply infrastructure which can be extracted and shared between multiple projects and is intended to provide a harness that minimises the amount of work required to add test cases, which, being the most numerous “code type” should ideally require the least amount of actual code individually. Both steps and page objects might feasibly considered as a form of “shared library”: re-use should most definitely be a goal and, once you begin to get a more comprehensive collection, you should not be required to always write new page objects or step implementations in order to write new test cases (but don’t move them out of the project until you absolutely have to!)
Consequently, with a little up-front work and thought, it is possible 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 practice that a somewhat lackadaisical approach at the outset leads to this aim being confounded: test harnesses become brittle, test code becomes more complex than the code it tests and has bugs, tests start to blink, the whole thing becomes disorganised and confused.
My aim throughout has been to balance flexibility with regularity and predictability: make the conventions clear and repeatable but not too rigid. From the perspective of the build cycle, one of the keys to achieving that predictability is making sure that developers can always run these kinds of test whenever they need to (reducing the time to feedback). Having a build infrastructure such as the one described here helps developers to avoid not only “breaking the build” but also “breaking the functionality”.
Clearly, there is code to write in order to actually make this test pass — which I shall hopefully come on to in subsequent posts — but that is one of the really nice things about BDD and functional testing of this kind: if you are using an agile methodology (which you should be), then your JBehave stories, whilst not absolutely equivalent, should bear a close relation to your iteration story cards. From this perspective, they can serve as a “definition of done” for any given vertical slice of functionality: you can proceed using TDD at a unit test level — the tests will fail & fail & fail — but, then, once all the various units are complete, 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 forget to do a little refactoring and code-tidying before you consider it truly complete.