You are on page 1of 64

Apache Isis BDD Testing Guide

Acceptance Testing using BDD Frameworks


Version 0.2.0-incubating

Copyright 2010~2011 Dan Haywood


Permission is granted to make and distribute verbatim copies of this manual provided that the copyright notice and this permission notice are preserved on all copies.

Apache Isis BDD Testing Guide

Preface ........................................................................................................................................ v 1. Introduction ............................................................................................................................ 1 1.1. Behaviour-driven Development ....................................................................................... 1 1.2. Common Library ........................................................................................................... 2 1.3. Concordion Integration ................................................................................................... 3 1.4. FitNesse Integration ....................................................................................................... 5 2. Introducing the Framework .................................................................................................... 7 2.1. Introduction ................................................................................................................... 7 2.2. Fixtures ......................................................................................................................... 8 3. Bootstrapping & Teardown .................................................................................................... 9 3.1. Scenario Context ............................................................................................................ 9 3.2. Bootstrapping Isis ........................................................................................................ 10 3.3. Shutdown Isis .............................................................................................................. 11 4. Scenario Set Up ..................................................................................................................... 13 4.1. Logging On / Switching User ........................................................................................ 13 4.2. Date and Time Format ................................................................................................. 14 4.3. Setting Date and Time .................................................................................................. 16 4.4. Aliasing Services ......................................................................................................... 17 4.5. Setting Up Objects ....................................................................................................... 18 5. User Interaction .................................................................................................................... 21 5.1. Common ...................................................................................................................... 21 5.2. Supported Interactions .................................................................................................. 24 5.3. Concordion Integration ................................................................................................. 28 5.4. FitNesse Integration ..................................................................................................... 30 6. Asserting on Collections ........................................................................................................ 31 6.1. Check Collection Contents ............................................................................................ 31 6.2. Check List ................................................................................................................... 32 6.3. Alias Items In List ....................................................................................................... 34 6.4. VerifyRows (Concordion only) ..................................................................................... 35 7. Debugging ............................................................................................................................. 37 7.1. Run Viewer ................................................................................................................. 37 7.2. Debugging the Clock .................................................................................................... 38 7.3. Debugging the Object Store .......................................................................................... 38 7.4. Check Specifications Loaded (FitNesse only) ................................................................. 39 7.5. Debugging Services (FitNesse only) .............................................................................. 39 8. Hints and Tips ...................................................................................................................... 41 8.1. Structure your scenarios using Given/When/Then ........................................................... 41 8.2. Use a Story Page to collect together its set of Scenarios .................................................. 41 8.3. Use a Top-Level Suite Page to Collect a Set of Stories ................................................... 42 8.4. Factor out common "Given"s ........................................................................................ 43 8.5. Use a Declarative Style for Page Names ........................................................................ 43 8.6. Separate In-Progress Stories from the Backlog ............................................................... 43 8.7. Organize Completed Stories by Component ................................................................... 44 8.8. Using the RunViewer fixture ........................................................................................ 44

iii

Apache Isis BDD Testing Guide

Contents

8.9. Set up Continuous Integration ....................................................................................... A. Using XmlMind with Concordion ........................................................................................ A.1. Customization to support Concordion ........................................................................... A.2. Creating a Document ................................................................................................... A.3. Loading a Document ................................................................................................... A.4. Navigating the Document ............................................................................................ A.5. Knowing where you are ............................................................................................... A.6. Selecting Content (eg to delete/move, or prior to adding new content) ............................. A.7. Writing Content .......................................................................................................... A.8. Inserting Content to existing Paragraphs ....................................................................... A.9. Deleting Content ......................................................................................................... A.10. Moving Content ........................................................................................................ A.11. Adding concordion: and isis: attributes ...............................................................

44 45 45 46 46 47 48 49 50 52 55 55 55

iv

Apache Isis BDD Testing Guide

Preface
Behaviour-driven development is a means to drive the development of an application through stories and scenarios. These are expressed in a semi-formal textual form that can be understood (or indeed be written) by the domain expert/business analyst, but which can then be used to directly exercise the system under test as it is developed. A number of frameworks exist to streamline this process. Generally these require the developer to write glue code that acts as a bridge from the textual specification and the system under test. The BDD Viewer module for Apache Isis aims to allow BDD stories/scenarios to be written against the domain model of an Isis application, without the developer having to write any glue code. It consists of a common library that abstracts the interaction with the Isis metamodel, along with an integration (that uses this common library) for one particular BDD framework, namely Concordion. There is also outline coverage of the FitNesse integration (part of isis-extras). This user guide describes how to use the Concordion integration, along with details of the common library so that other BDD frameworks can be integrated if required. Apache Isis is licensed under Apache Software License v2. However, although Concordion itself licensed under Apache License v2, it in turn depends upon an XML library called XOM, which unfortunately has an LGPL 2.1 license. Apache projects are not allowed to have dependencies on LGPL projects. The workaround that we have adopted is to exclude the XOM dependency in Isis' own pom.xml files, meaning that they are compliant with Apache's licensing restrictions. However, any application code that uses the BDD Viewer must explicitly add its own dependency to the XOM library. You'll find that the pom.xml files generated by the quickstart archetype do indeed do this. However, If you are unhappy to introduce this dependency to LGPL in your own code, then you will not be able to use the Concordion integration.

Chapter 1

Introduction

An introduction to the idea of behaviour driven development, and the components that make up Isis' integration with BDD frameworks.

1.1. Behaviour-driven Development


Prior to agile development, requirements gathering for systems was traditionally performed by business analysts discussing requirements with the business, and expressing those requirements in documentation, such as Word specs and perhaps spreadsheets. The acceptance criteria for such requirements were often only sketched out, if at all; it would normally fall to the system testers to write acceptance tests for the requirements, through a mixture of consulting the original (by now out-of-date) requirements documentation and (as often as not) reverse-engineering the implementation. Behaviour-driven development combines requirements capture and the acceptance test criteria in a single form, through scenarios. As before, these requirements are in a form that a non-technical domain expert from the business can understand. What differs though is that these scenarios can be used to directly exercise the system, and so also represent the acceptance tests for the correct implementation of the requirement. Moreover, the results of these tests are rendered in such a way that the business can understand, and thus can help determine if the code is at fault or the test. Once implemented, the acceptance tests also act as a regression suite for the system. Scenario tests tend to act against a complete system, or sometimes at a subsystem-level. At any rate the tests must be at a granularity that still makes sense to a non-technical businesss person. Compare this to unit testing which exercises the behaviour / method of a single class. Another commonly-used name for scenario testing is "agile acceptance testing". We've chosen to use the term "scenario testing" though; it's a somewhat less clumsy term.

Introduction

Common Library

1.2. Common Library


Apache Isis integrates with BDD frameworks through the services of a common library. The main concepts that the common library exposes are: the Scenario class, which provides the context for a single scenario of a story the AliasRegistry, which allows a user-friendly alias (eg "fredCustomer") to be assigned to any domain object and to be referenced subsequently the StoryCell interface, which is an abstraction over a single element of data The default implementation just wraps a java.lang.String, but some frameworks might provide alternative implementations. For example, FitNesse has an implementation that maps to its internal representation of a cell (fit.Parse class). the CellBinding interface, which binds a column of a table to a property or to an alias Many of the BDD frameworks (eg Concordion, FitNesse) encourage the use of tables as a means of succinctly capturing scenario actions. This is reflected in the design of several of the classes provided by the common library. The CellBinding interface is used to wire the values in the rows of the table to the properties specified in the header. Each CellBinding has a name (eg "on object") and some alternative names (eg "using"). This is useful for BDD frameworks (such as FitNesse) where the provided text from the scenario needs to be parsed in some way, matching up headings of columns within a tabular structure. The relevant methods for BDD framework integrations that must do this are #matches(...), #setHeadColumn(..) and #createHeadColumn(..). Note though that not every BDD framework integration needs this particular feature; the Concordion integration for example calls pre-canned methods so the matching is done simply by parameter position to these method. The other main method provided by CellBinding is #captureCurrent(..). This is used (by all framework integrations) to capture the current value for the column which this binding represents. For example, for a column representing a property name, it might hold the value "firstName". The library provides a default implementation of CellBinding, though subclasses can override if required. (For example, the FitNesse integration has its own implementation to map to its data structures representing cells in the FIT tables). One way of thinking of all these framework integrations is as a replacement presentation layer, hitting the underlying domain model in the same way that the regular UI would. (This is why we call this module is called the BDD viewer). In the following chapter (Chapter 2, Introducing the Framework), details are provided of how the services of the common library are used by each of the supported BDD framework integrations.

Apache Isis BDD Testing Guide

Introduction

Concordion Integration

1.3. Concordion Integration


Introduction to Concordion
Concordion is a framework to enable scenario testing. It is implemented as a JUnit4 test runner, with the test form being written in XHTML. The domain expert / business analyst authors new stories using an XML editor; once executed as tests, the results are shown as the same XHTML document, annotated to indicate which assertions have succeeded, and which have failed. It also creates an efficient feedback loop; a Concordion test will "keep on going" even if it hits a failure. Thus the developer can identify several issues and fix them in a single pass. Concordion works using a "convention over configuration" approach, matching the XHTML text file with a corresponding JUnit4 test run set up to run using Concordion's ConcordionRunner, The developer then annotates the XHTML using special (namespaced) attributes in order identify the inputs to and expected results of the test. This is used by the ConcordionRunner to call into corresponding methods in the test. For example: Suppose the analyst writes a scenario test called CustomerPlacesOrderScenario.xhtml. In the XHTML the analyst has identified the details of the customer doing the ordering (customer ref 4321, say), the product being ordered (product code 1234), the fact that the customer initially has no orders, and that the customer has no invoices outstanding. The test concludes with an assertion that there is now an unfulfilled order for the customer, and that the customer now has an invoice to be paid. The developer in turn edits the XHTML, identifying the customer and the product. He then further edits the XHTML to call a method in the JUnit4 test representing the placing of an order: placeOrder(), say. And he finishes by annotating the XHTML to make assertions about the post conditions (unfulfilled order, new invoice to be paid etc). Then, the developer writes a JUnit4 test alongside the XHTML; in this example it would be called CustomerPlacesOrderScenario.java. Concordion calls into this JUnit4 test as it comes across the annotations in the XHTML, and the JUnit4 test mediates with the system under test. When the test runs, Concordion generates a copy of the XHTML in an output directory which can then made available for inspection by the business analyst (eg published on a website). The Concordion website has a good tutorial that demonstrates all the above, and can be completed in 20~30 minutes. One slight downside of using Concordion is in having to write tests in XHTML. One editor that we recommend (commercial, but also with a free version for personal use) is XmlMind. More detailed guidance is provided in Appendix A, Using XmlMind with Concordion.

How the Isis/Concordion Integration Works


Although you could test an Apache Isis application using vanilla Concordion, this would entail you having to write all the glue code yourself to interact with the domain objects. You would also need to encode the

0.2.0-incubating

Introduction

Specifying the Output Directory

rules that are normally implemented by an Isis viewer, eg so that a hidden action cannot be invoked, and an invalid value for a property cannot be set. The BDD viewer integration provided by Apache Isis works by providing a superclass for the JUnit4 test, called AbstractIsisConcordionScenario. This exposes methods to perform all the tasks necessary for exercising an application. The precise features are outlined in Chapter 2, Introducing the Framework. For test, the developer writes subclasses the AbstractIsisConcordionScenario, creating a name matching the scenario test (ie as per regular Concordion). He then annotates the original XHTML, either calling directly into the inherited methods, or writing small simple methods to delegate to these inherited methods as required. The Concordion website has some hints and tips to help you find the right balance between these two approaches. The XHTML script that you write should have the following namespace declaration:
<html xmlns:concordion="http://www.concordion.org/2007/concordion" xmlns:isis="http://isis.apache.org/2010/concordion"> ... </html>

each

XHTML

scenario

The concordion namespace is the usual namespace required by Concordion. The isis namespace is defined for a similar reason: to allow certain commands provided by the Isis/Concordion integration to be invoked. More on this in Chapter 5, User Interaction.

Specifying the Output Directory


The directory for the generated output can be specified either: by overriding the outputDir() method in AbstractIsisConcordionScenario; or by setting the concordion.output.dir system property If not specified, then the output directory defaults to /tmp/concordion.

Providing a CSS File


By default, Concordion will copy over the HTML for every scenario into the output directory, but it won't copy over any CSS resources. If you want any CSS to be copied over, then: override the customCssPackage() method in AbstractIsisConcordionScenario to return any class in the package that holds the CSS file. override the customCss() method to specify the name of the CSS file to copy over. If none is specified, then concordion.css is used.

Configuring the Maven Surefire (test) plugin


The standard boilerplate to run Concordion under Maven is as follows:
<plugins>

Apache Isis BDD Testing Guide

Introduction

FitNesse Integration

<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.6</version> <configuration> <systemPropertyVariables> <concordion.output.dir> ${project.build.directory}/concordion </concordion.output.dir> </systemPropertyVariables> <includes> <include>**/*Scenario.java</include> <include>**/Scenario*.java</include> </includes> </configuration> </plugin> </plugins>y

There are a couple of points worth noting here. first, the systemPropertyVariables element can be used to define the concordion.output.dir system property, thereby specifying the directory for the generated output second, the includes element can be used to only run classes with either a prefix or suffix "Scenario". This allows common fixtures that have been factored out to be ignored. An alternative approach is to have a top-level "suite" page that references all scenarios underneath (probably grouped into stories). In this case the only test class that would be run included is the toplevel suite page. See Section 8.3, Use a Top-Level Suite Page to Collect a Set of Stories for further discussion.

1.4. FitNesse Integration


Due to licensing restrictions, the FitNesse integration is not part of Apache Isis proper. However, it is available on the companion Apache Extras' isis-extras site. Check that site for its release status (it is not guaranteed to be in sync with Isis releases). An outline of the FitNesse integration is provided here, if only to help compare and contrast the means by which two different frameworks integrate with the common library. We hope that this will make it easier to integrate other BDD frameworks in the future.

0.2.0-incubating

Chapter 2

Introducing the Framework

An introduction to the features provided by the framework. The subsequent chapters provide more detailed coverage.

This chapter outlines the main features of the common library and their support by the framework-specific integrations. The subsequent chapters provide more detailed coverage. Note that due to licensing restrictions the FitNesse integration is not part of Apache Isis. Nevertheless, an outline of the FitNesse integration is provided here, if only to help compare and contrast the means by which two different frameworks integrate with the common library. We hope that this will make it easier to integrate other BDD frameworks in the future.

2.1. Introduction
Broadly speaking, the framework provides the ability to bootstrap and initialize an Isis application and allow the domain services and objects within that application to be exercised in the same way that a user would interact with the system through a viewer. The common library defines these abilities in terms of "fixture" classes, each of which performs a single function. For example, there is a fixture class to bootstrap Isis, another to setup objects, and another to describe the actual interactions by the user (check a property, invoke an action etc). The fixture classes in the common library are oriented around a tabular approach to specifying behaviour, making it easy to integrate frameworks such as FitNesse that adopt a table-oriented approach. Such an approach equally supports frameworks such as Concordion that allow specifications to be written both as tables and in free-form text. Admittedly, this does make the implementation of framework integrations

Introducing the Framework

Fixtures

a little more complex than it might otherwise have been ... but this is only a problem for the framework integrator, not the business analyst actually writing the scenarios.

2.2. Fixtures
The following chapters describe the fixtures available in detail. In summary, they are: it bootstrap an instance of Apache Isis system using the in-memory object store (see Chapter 3, Bootstrapping & Teardown); setting up the system state ready for the scenario (see Chapter 4, Scenario Set Up): specify the date/time format initialize the system with a set of services, picked up from the isis.properties configuration file allow fixtures (domain objects) to be installed into the object store login a specific user specify the date allow the user to interact with services and domain objects (see Chapter 5, User Interaction): asserting on the value of properties and the contents of collections setting the value of a property (if valid) and adding to/removing from a collection (if valid) invoking actions asserting on the state of a class member (hidden, disabled or enabled) assert on the state of properties assert on the state of collections, either of an object, or returned from an action (see Chapter 6, Asserting on Collections); tearing down the system at the end of the test (see Chapter 3, Bootstrapping & Teardown) There are also fixtures to help with debugging (see Chapter 7, Debugging). For each fixture, you'll find there's a discussion about the capabilities provided by the common fixture, and then details as to the support for that fixture by each of the BDD framework integrations (Concordion and FitNesse).

Apache Isis BDD Testing Guide

Chapter 3

Bootstrapping & Teardown

The fixtures provided for bootstrapping at the start of a scenario, and tearing down at the end

In order to test an Apache Isis domain application, a running instance of an IsisSystem must be bootstrapped, with the appropriate configuration.

3.1. Scenario Context


The common library provides a context object which holds a reference to a running IsisSystem. Moreover, it tracks such things as the date/time that the scenario is running as, the user that is logged-in, and managing the aliases of objects so that they can be interacted with.

Common
An instance of the Scenario class provides a context for the scenario. Framework integrations are expected to instantiate this class, and then use it as the primary means to interact with the system. The Scenario class has a public no-arg constructor. Instantiating the Scenario does not do anything; it must also be bootstrapped (see Section 3.2, Bootstrapping Isis).

Concordion
The AbstractIsisConcordionScenario instantiates the Scenario object (from the common library) automatically and binding it to a threadlocal. In addition, AbstractIsisConcordionScenario provides methods that can be invoked from within XHTML (ie taking Strings).

Bootstrapping & Teardown

FitNesse

Test cases should inherit from this abstract class, with the XHTML typically calling to the inherited methods directly. The developer may optionally add small helper methods to be called from the XHTML instead; these can factor out any boilerplate in the script.

FitNesse
Every FitNesse scenario must reference the ScenarioFixture fixture which provides the overall context for the framework. This instantiates a Scenario object from the common library and binding it to a thread-local. Whereas other FitNesse fixtures are instantiated once per table, the ScenarioFixture is a FIT DoFixture that exists for the duration of the test page. It should typically be referenced in the test suite's setup page, and should appear first within this setup:
Scenario Fixture

3.2. Bootstrapping Isis


An Isis runtime can be bootstrapped with a single call. This installs no-op implementations of some of the main components, along with an in-memory object store.

Common
The
Scenario#bootstrapIsis(String configDirectory, DeploymentType

deploymentType) is used to bootstrap the Isis runtime:

The specified config directory contains isis.properties config file, from which the services are registered. Any fixtures in that properties file are ignored (the BDD integration requires that any objects are created through the test scripts, see Section 4.5, Setting Up Objects and Chapter 5, User Interaction). The deployment type must be either EXPLORATION (meaning exploration actions are enabled) or PROTOTYPE; no other values are valid). Even if running in exploration mode, you must still logon (see Section 4.1, Logging On / Switching User) in order to indicate which user account to run the scenario as.

Concordion
The
AbstractIsisConcordionScenario

class

provides

two

overloaded

versions

of

bootstrapIsis(...) method:

#bootstrapIsis(String configDirectory, DeploymentType deploymentType) Intended to be called from within an @Before setUp() method, when there's no particular need to document the bootstrapping process within the scenario;

10

Apache Isis BDD Testing Guide

Bootstrapping & Teardown

FitNesse

#bootstrapIsis(String configDirectory, String deploymentType):boolean Intended to be called from the XHTML page, allowing the scenario document the bootstrapping process. For example, to bootstrap in exploration mode, use:
<p concordion:execute="#result=bootstrapIsis(#configDir,#deploymentType)"> Isis system <span concordion:assertTrue="#result">bootstrapped</span> from config directory <span concordion:set="#configDir">../quickrun/config</span> and running in <span concordion:set="#deploymentType">exploration</span> mode. </p>

The method always returns true, but any runtime exception will propagate to the generated page. Whichever method is used, they both delegate to the common Scenario#bootstrapIsis(...) method.

FitNesse
The BootstrapIsisConfiguredFromInMode fixture is used to bootstrap Isis. It takes the form:
Bootstrap Isis Configured From

config Directory

In Mode

deployment Type

This delegates to the common Scenario#bootstrapIsis(...) method.

3.3. Shutdown Isis


This fixture shuts down the Isis runtime, releasing memory and so on. A good place to put this is in the test's teardown.

Common
The Scenario#shutdownIsis() method is used to shutdown Isis runtime.

Concordion
To Concordion, use the AbstractIsisConcordionScenario#shutdownIsis() method. This just delegates to the common library's Scenario#shutdownIsis() method. shutdown Isis from within

FitNesse
To shutdown Isis from FitNesse, use the ShutdownIsis fixture:
Shutdown Isis

0.2.0-incubating

11

Bootstrapping & Teardown

FitNesse

This just delegates to the common library's Scenario#shutdownIsis() method.

12

Apache Isis BDD Testing Guide

Chapter 4

Scenario Set Up

Once Isis has been bootstrapped, the application state must be setup.

The setup fixtures are used to specify the initial state of the running application for a particular scenario's. Specifically, this means setting up the services that define the application, the effective date and the effective user. It also allows the setup of arbitrary objects (typically reference/static data objects; for transactional objects see Chapter 5, User Interaction).

4.1. Logging On / Switching User


Used to specify the currently logged-on user. Should always be called near the top of the scenario, as part of the "given". Can also be used for switching the current user later on in the scenario, eg to check a workflow between different users.

Common
The common library provides two overloaded methods, depending on whether the roles for the user need to be specified or not: Scenario#logonAsOrSwitchUserTo(String userName) Logs on / switch user to as a specific user. Scenario#logonAsOrSwitchUserTo(String userName, List<String> roleNames) Logs on to a specific user, with specified roles. Part of the initialization for a particular scenario's setup, and typically referenced in the test suite or scenario's own setup page.

13

Scenario Set Up

Concordion

The username and roles are not validated against.

Concordion
The provides AbstractIsisConcordionScenario: Concordion integration two sets of overloaded methods in

#logonAs(String userName) and #logonAs(String userName, String roleListStr) Intended to be called in the initial setup, as part of the scenario's "given". #switchUserTo(String
roleListStr) userName) and #switchUserTo(String userName, String

(Optional); intended to be called later on in the scenario, eg, to test workflow. Each of these is intended to be called from the XHTML. For example:
<p concordion:execute="#result=logonAs(#userName)"><span concordion:assertTrue="#result">logged on</span> as <span concordion:set="#userName">fsmith</ span></p>

The role list, if specified, should be comma-separated (any white space will be ignored).

FitNesse
The FitNesse integration provides two sets of overloaded fixtures:
Logon As Logon As

user name user name


With Roles

role list

Intended to be called in the initial setup, as part of the scenario's "given".


Switch User To Switch User To

user name user name


With Roles

role list

Optional; intended to be called later on in the scenario, eg, to test workflow. The role list, if specified, should be comma-separated (any white space will be ignored).

4.2. Date and Time Format


BDD tests often rely on exact dates and/or time to be specified, but any such date/time must be specified in text form. In order that tests do not fail when run with different locales, the common library allows a date/time format to be specified. This fixture is typically called only once in the scenario.

14

Apache Isis BDD Testing Guide

Scenario Set Up

Common

Note
Date/time formats are based on the currently used locale. By default, Isis will use the current locale. To change this, set the relevant locale within isis.properties. For example:
isis.locale=de_DE

Note
Dates are always intepreted strictly as UTC dates. This means that you shouldn't need to worry about the timezone in which the tests are being run.

Common
The Scenario class provides two methods to specify date and time formats: #usingDateFormat(String) is used to specify the date format. If not called, the format defaults to "dd-MMM-yyyy", eg 02-Aug-2010. #usingTimeFormat(String) is used to specify the time format. If not called, the time format defaults to "hh:mm" (a 24 hour clock, eg 14:45 for 2.45pm) Typically the scenario will have both date and also time specified (in Section 4.3, Setting Date and Time), but assertions against domain objects will often care only about date, or may occasionally care only about time. Therefore, whenever text representing a date must be parsed, the following is used: parse against date+time (eg "dd-MMM-yyyy hh:mm") if that fails, parse against just date (eg "dd-MMM-yyyy") if that fails, parse against just time (eg "hh:mm")

Concordion
The AbstractIsisConcordionScenario provides two corresponding methods: #usingDateFormat(String) #usingTimeFormat(String) For example:
<p concordion:execute="#result=timeIs(#dateTime)">The <span concordion:assertTrue="#result">date/time</span> is <span concordion:set="#dateTime">2 mar 2007 09:20</span>.</p>

These just delegate to the corresponding methods in the Scenario class.

FitNesse
Not implemented at this time.

0.2.0-incubating

15

Scenario Set Up

Setting Date and Time

4.3. Setting Date and Time


BDD scenarios often rely on an exact date and time, and the date/time that a scenario is being run upon can be specified using this fixture. For example, with the date/time set, functionality that checks the a property is defaulted to "today" can then be easily verified. This fixture will typically be called only once in a scenario. However, more advanced scenarios might require the date/time to be called different places. For example, a scenario might raise an Invoice, then move the clock forward by 30 days to test functionality relating to the handling of Invoices unpaid for more than 28 days.

Common
The Scenario#dateAndTimeIs(String)method allows the scenario to specify the date and time. Note that a String is passed in rather than a java.util.Date, so that the scenario can parse the date according to the date/time format (see Section 4.2, Date and Time Format). The fixture installs the FixtureClock as the implementation of the Clock singleton (in the applib). Every call to the Clock will return the same date/time until the method is called again. If this fixture is not called, then the default system clock is used, which gets the time from the host computer. The Scenario#debugClock() method (Section 7.2, Debugging the Clock) can be used to verify the clock state.

Concordion
The Concordion integration provides a number of overloaded methods, all designed to be called from the XHTML: #dateIs(String dateAndTimeStr) #timeIs(String dateAndTimeStr) For example:
<p concordion:execute="#result=timeIs(#dateTime)"> The <span concordion:assertTrue="#result">date/time</span> is <span concordion:set="#dateTime">02-mar-2007 09:20</span>. </p>

The overloaded forms are just for convenience; sometimes the scenario will want to emphasis the date, other times the time.

FitNesse
The FitNesse integration provides four versions (overloaded only so reads well in the page):
Date Is

date and time

16

Apache Isis BDD Testing Guide

Scenario Set Up

Aliasing Services

Date Is Now

date and time date and time date and time

Time Is

Time Is Now

In each case the date/time provided is parsed against the format 'dd MMM yyyy hh:mm'

4.4. Aliasing Services


Specifies an alias to a service in order to invoke actions upon it. Typically this will be done for most if not all of the registered repositories. The class name (as defined in isis.properties) is used as the key; the alias defines a simple handle. For example, a service com.mycompany.customers.defaults.CustomerRepositoryDefault can be mapped to "customers".

Common
The common library provides two methods: Scenario#getAliasRegistry() AliasRegistry#aliasService(String aliasAs, String serviceClassName) The BDD framework integration is expected to obtain the AliasRegistry from the Scenario, and then use the AliasRegisty to register the alias.

Concordion
The Concordion integration provides a corresponding method, #aliasService(aliasAs, String serviceClassName). This returns true if the service was found, false otherwise. Call within a table to alias multiple services, for example:
<table concordion:execute="#result=aliasService(#aliasAs, #className)"> <tr> <th concordion:set="#className">Class Name</th> <th concordion:set="#aliasAs">aliasAs</th> <th concordion:assertTrue="#result"/> </tr> <tr> <td>com.mycompany.myapp.objstore.dflt.claim.ClaimRepositoryDefault</td> <td>claims</td> <td>ok</td> </tr> <tr> <td>com.mycompany.myapp.objstore.dflt.employee.EmployeeRepositoryDefault</td> <td>employees</td> <td>ok</td>

0.2.0-incubating

17

Scenario Set Up

FitNesse

</tr> </table>

FitNesse
The FitNesse integration provides an implementation of a ColumnFixture, which is used as follows:
Alias Services class name alias=

com.mycompany.myapp.objstore.dflt.claim.ClaimRepositoryDefault claims

4.5. Setting Up Objects


Virtually every scenario will require some initial objects to work on, be it a Customer, an Order or just some reference data. However, by design the BDD viewer runs against an in-memory object store, meaning that the application having been bootstrapped has nothing in its persistent object store. This fixture, therefore, is used to create objects, and persists them to the object store. It is typically used for immutable reference/standing data objects. It can also be to setup used for transaction/operational data objects, though UsingIsisViewerForSetup, Chapter 5, User Interaction, is generallly to be preferred). The DebugObjectStore fixture (Section 7.3, Debugging the Object Store) can be used to check the state of objects created. You can also use the RunViewer fixture (Section 7.1, Run Viewer) to visually inspect the state of the system using the DnD viewer.

Common
The common library support for setting up objects using the SetUpObjectsPeer class. This represents the context for creating a set of objects all of the same type, and is usually called multiple times (eg corresponding to a table structure in the scenario text itself). The constructor for this class takes the following arguments: AliasRegistry aliasRegistry the alias registry which is used to lookup aliases to existing objects, and is populated with aliases for the new created objects (if an alias binding is specified; see below). String className This is the fully qualified class name of the object to be instantiated SetUpObjectsPeer.Mode mode This is whether the object is to be persisted or not CellBinding aliasBinding

18

Apache Isis BDD Testing Guide

Scenario Set Up

Concordion

This object represents a binding to a cell that will hold the reference to each newly created object. It can be left null if required. Different methods are available for BDD framework integrations to call. Typically the BDD framework is expected to setup header information (the names of the properties), and then process each row. On the header of the table, the main method to call is: #definePropertyOrAlias(String propertyNameOrAlias, int colNum) This associates each column with a property of the class, or an alias for the object overall When processing each row, typically the main methods to call are: #addPropertyValueOrAlias(String propertyOrAliasValue) This provides the value of each property of the object to be created, or the alias to know the object by once created. The property value can either be an existing alias, else must be parseable (nb: Isis' own value types itself perform the parsing, so there's no additional work to be done here) #createObject() This actually instantiates the object, either persistent or non-persistent as specified in the constructor, and assigns it an alias That said, there are some other public methods that are available for more complex integrations (notably: FitNesse).

Concordion
The Concordion framework integration provides: #setUpObject(String className, String aliasAs, String propertyName1, String
propertyName2, ...)

There are 10 overloaded versions of this method, to account for setting up different types of objects that have up to 10 properties. The method returns a string "ok" if has worked, otherwise it returns exception text. This might seem a little odd, but allows a meaningful message to be shown in the XHTML. #setUpObjectVarArgs(String className, String aliasAs, ...) This (protected, not public) method is to cater for setting up objects that require more than 10 properties to be setup. In these cases, the developer should write their own method and call into the #setUpObjectsVarargs(...) as required. Note that this method should be called from the XHTML using isis:execute, not with concordion:execute. The difference between the two is that isis:execute is called on the header row as well as each body row, whereas concordion:execute only calls for each body row. The integration

0.2.0-incubating

19

Scenario Set Up

FitNesse

requires the header row to be called in order to read the names of the properties to be used to initialize the objects. For example, here's how to setup a set of three Employee objects:
<p>With Employees (<span concordion:set="#className">com.mycompany.myapp.dom.employee.Employee</span>): </p> <table isis:execute="#result=setUpObject(#className, #aliasAs, #name, #approver)"> <tr> <th concordion:set="#name">Name</th> <th concordion:set="#approver">Approver</th> <th concordion:set="#aliasAs">aliasAs</th> <th concordion:assertEquals="#result"/> </tr> <tr> <td>Fred Smith</td> <td></td> <td>Employee:Fred Smith</td> <td>ok</td> </tr> <tr> <td>Tom Brown</td> <td>Employee:Fred Smith</td> <td>Employee:Tom Brown</td> <td>ok</td> </tr> <tr> <td>Sam Jones</td> <td>Employee:Fred Smith</td> <td>Employee:Sam Jones</td> <td>ok</td> </tr> </table>

In this example, we've chosen the convention that the alias is "Employee:FirstName LastName". This is though just a convention; the alias could be anything you want.

FitNesse
The FitNesse integration uses the "Set Up Objects" table, called like so:
Set Up Objects Name

com.mycompany.myapp.dom.employee.Employee
Approver alias as

Fred Smith Tom Brown Sam Jones Employee:Fred Smith Employee:Fred Smith

Employee:Fred Smith Employee:Tom Brown Employee:Sam Jones

20

Apache Isis BDD Testing Guide

Chapter 5

User Interaction

Fixtures to describe interactions with the domain objects, mimicking the way in which an end-user using an Isis viewer would interact.

The user interaction fixtures are the centrepiece of the BDD framework, simulating the interaction with domain objects as if through a viewer. Using this fixtures, the scenario can interact with objects, check their state, and alias referenced or returned objects for subsequent interactions There is basically just one fixture used to describe user interactions, namely "Using Isis Viewer". There is also a "For Setup" version (ie "Using Isis Viewer For Setup") that disables checks for visibility and usability, making it easier to reuse functionality for setting up objects prior to a test scenario (the "given"). The DebugObjectStore fixture (Section 7.3, Debugging the Object Store) can be used to check the state of objects created.

5.1. Common
The common library provides the UsingIsisViewerPeer class as a means by which the BDD framework integration can interact with Apache Isis runtime. The UsingIsisViewerPeer class is generally called from within a table format, with each row representing a specific interaction with the domain object. For example, a row might invoke an action, or could check that a class member is unavailable. Some interactions can be used to create or assign aliases to domain objects. For example, invoking a non-void action will return a result. If the result is a domain object, then the alias can be used directly subsequently in the scenario. If the result is a collection, then typically it is the scenario will make an

21

User Interaction

Constructor

assertion on that collection using "Check List" (see Section 6.2, Check List) or alias an object out of that list using "Alias Items In List" (see Section 6.3, Alias Items In List).

Constructor
Because UsingIsisViewerPeer is table-oriented, it uses CellBindings (see Section 1.2, Common Library) to bind table headers to rows. The constructor takes the following parameters: AliasRegistry Used to access aliases for existing domain objects, and to register aliases for newly created/found objects. Perform.Mode Whether to actually perform the interactions or not CellBindings for each of the columns of the table. Cell bindings are discussed immediately below. Each of the framework integrations is expected to instantiate the UsingIsisViewerPeer at the beginning of the table, and then call into the same instance for each row in the table. Cell Bindings The CellBindings passed into the constructor correspond to the standard columns of the table. Although all must be passed in, not all are needed for every interaction; in these cases the value can be left blank. The CellBindings correspond to the following column names: the "on object" column (can also use 'object', or 'on' if parsing column name provided by scenario text) The (alias of) the object to interact with. A value must always be provided. the "alias result as" column (can also use "result=", "alias=", "alias as") The alias to assign the result of any interaction. the "perform" column (can also use "do", "interaction", "interaction type") the interaction to perform; discussed further below the "on member" column (can also use "member", "using member", using") the property, collection or action to use the "that it" column (can also use "that", "verb") optional qualifier for interactions that make checks; discussed below the "with arguments" (can also "arguments", "parameters", "with parameters", "for", "value", "for parameters", "value", "reference")

22

Apache Isis BDD Testing Guide

User Interaction

Capture Current

the first argument, to the interaction, if any. It is possible to perform interactions with multiple arguments (for example, invoking an action); but the UsingIsisViewerPeer needs to have a binding for the first argument so that it can knows to interpret any following columns as further arguments. The actual values that go into each of these columns are listed below (Section 5.2, Supported Interactions). The "Perform" Binding Of all of the bindings discussed above, the "perform" binding is the most critical because it determines the actual type of interaction to be performed. The valid values that can be provided for the "perform" binding are: check property / check collection / check add to collection / check remove from collection / check action These are combined with a value in the "that it" binding; for example "check property XXX is hidden", or "check action XXX is valid for (some argument list)" get property / set property / clear property Read from or write to a collection. If setting, a single argument is required get collection / add to collection / remove from collection Read or write from a collection. If writing, a single argument is required invoke action Invoke action, with 0 to many arguments get property default / get property choices / get action parameter default / get action choices To enable the testing of the choicesXxx() and defaultXxx() supporting methods Again, see the sections below (Section 5.2, Supported Interactions) for specifics..

Capture Current
Once the bindings have been setup, the fixture peer should be called for each row in the table. The CellBinding class provides the #captureCurrent(...) method to capture the relevant value for each row (with the CellBindings obtained directory from the Scenario class, eg Scenario#getOnObjectBinding()). For some framework integrations (eg Concordion) this design introduces a little more complexity than strictly necessary, because the knowledge is already known as to which value relates to which binding. But for other frameworks (eg FitNesse), the CellBinding provides a useful abstraction that makes it easy to associate values with each column.

0.2.0-incubating

23

User Interaction

Validate

Validate
Once the values for the current row have been captured, they can be validated. The UsingIsisViewerPeer class provides the following methods for this: #validateOnObject(): ObjectAdapter Verifies that the current value of the "on object" binding corresponds to a known alias #validateAliasAs(): String Verifies that the current value of the "alias as" binding is not already in use #validateOnMember(): ObjectMember Verifies that the current value of the "on member" binding corresponds to the name of a member (property name, collection name or action name) of the type of the object being interacted with (ie, as specified in the "on object" binding) #validatePerform(): Perform Verifies that the current value of the "perform" binding corresponds to a known interaction type for the particular type of member. Again, see the sections below (Section 5.2, Supported Interactions) for specifics.

Perform Command
Once all the validation has been performed, the command can actually be performed. This is done with the UsingIsisViewerPeer's #performCommand(ObjectAdapter onObject, String aliasAs, ObjectMember onMember, Perform perform, List<ScenarioCell> args) method.

5.2. Supported Interactions


The valid values for the various bindings when interacting with a class members are summarized in the following sections. Note: the API provided by the common library is not type-safe; the values (as provided in ScenarioCell) must match the values given here. While it is tempting to refactor the common library to use type safe enums, this would move the need to translate scenario text into each and every BDD framework integration. The API is probably correct as it is, even though it is reliant on the exact string phrases that appear in the tables above.

Interaction with Properties


The valid values for the various bindings when interacting with a property are summarized below:

24

Apache Isis BDD Testing Guide

User Interaction

Interaction with Properties

on object perform Table 5.1. Supportedalias as Interactions for Properties object alias object alias object alias object alias object alias object alias object alias object alias object alias object alias object alias object alias object alias object alias object alias alias for referenced object alias for default object alias for list of choices
check property check property check property check property check property check property check property check property check set

using member property name property name property name property name property name property name property name property name property name property name property name property name property name property name property name

that it
is hidden

value

is visible

is disabled

is enabled

is empty

is not empty

contains

value or object alias value or object alias value or object alias value or object alias

does contain is for is

not

valid

property check set

not

property check clear property check clear property set property clear property get property

valid for is valid

is not valid

value or object alias

object alias

get property default

property name

object alias

get property choices

property name

0.2.0-incubating

25

User Interaction

Interacting with Collections

Obtaining a alias for the (value of) a property only makes sense if the property is a reference type, not value type.

Interacting with Collections


The valid values for the various bindings when interacting with a collection are summarized below:

26

Apache Isis BDD Testing Guide

User Interaction

Interacting with Collections

Table 5.2. Supported Interactions for Collections on object object alias object alias object alias object alias object alias object alias object alias object alias object alias object alias alias for collection alias as perform
check collection check collection check collection check collection check collection check collection get collection check collection check collection check to collection add

using member collection name collection name collection name collection name collection name collection name collection name collection name collection name collection name collection name collection name collection name collection name collection name

that it
is hidden

reference

is visible

is disabled

is enabled

is empty

is not empty

contains

object alias object alias object alias

does contain is for

not

valid

object alias

check to

add

is

not

object alias

valid for

collection

object alias

check remove from collection

is for

valid

object alias

object alias

check remove from collection

is

not

object alias

valid for

object alias object alias

add

to

object alias object alias

collection remove from collection

0.2.0-incubating

27

User Interaction

Interacting with Actions

Obtaining a reference to a collection allows objects to be aliased from within it, using Section 6.3, Alias Items In List.

Interacting with Actions


The valid values for the various bindings when interacting with an action are summarized below: Table 5.3. Supported Interactions for Actions on object alias as perform using member that it with arguments (one or more cols)

object alias object alias object alias object alias object alias object alias object alias alias returned object for

check action check action check action check action check action check action invoke action

action name action name action name action name action name action name action name

is hidden

is visible

is disabled

is enabled

is for is

valid

argument list argument list argument list

not

valid for

object alias

alias for parameter defaut alias for list of parameter choices

get

action

action name

default parameter get action

param number (0-based) param number (0-based)

object alias

action name

parameter choices

5.3. Concordion Integration


The integration provides a set of overloaded AbstractIsisConcordionScenario which call into the UsingIsisViewerPeer: #usingIsisViewer(String
String usingMember) onObject, String aliasResultAs,

Concordion

framework

methods

in

String

perform,

28

Apache Isis BDD Testing Guide

User Interaction

Concordion Integration

For interactions that have no "that it" or arguments (eg "get collectoin recentlyPlacedOrders") #usingIsisViewerThat(String onObject, String aliasResultAs, String perform,
String usingMember, String thatIt)

For interactions that require a "that it" but no arguments (eg, "check property firstName that it is hidden") #usingIsisViewerArgs(String onObject, String aliasResultAs, String perform, String usingMember, String arg0, String arg1, ...) For interactions that require arguments, but no "that it" (eg "invoke action placeOrder with arguments arg1, arg2, arg3"). There are multiple overloaded versions of this method taking from 1 to 5 arguments. #usingIsisViewerThatArgs(String
onObject, String aliasResultAs, String perform, String usingMember, String arg0, String arg1, ...)

For interactions that require a "that it" and also an argumetn or arguments (eg "check action placeOrder is not valid for arg1, arg2, arg3) There are multiple overloaded versions of this method taking from 1 to 5 arguments. If there is a requirement for more than 5 arguments, then you can write your own method and delegate to the (protected visibility) #usingIsisViewerThatArgsVarargs(...) method. In all cases these methods return the string "ok", or return the text of an exception otherwise. This makes them easy to embed Do note that this method, if called from a table should be called from the XHTML using isis:execute, not with concordion:execute. This is because the Isis/Concordion integration requires that the first header row of the table also be processed (the concordion:execute only processes every row of the body but skips the table). For example:
<table isis:execute="#result=usingIsisViewerThatArgs(#onObject, #aliasResultAs, #perform, #onMember, #thatIt, #value)"> <tr> <th concordion:set="#onObject">on object</th> <th concordion:set="#aliasResultAs">alias result as</th> <th concordion:set="#perform">perform</th> <th concordion:set="#onMember">on member</th> <th concordion:set="#thatIt">that it</th> <th concordion:set="#value">value</th> <th concordion:assertEquals="#result" /> </tr> <tr> <td>tomEmployee</td> <td>tomsApprover</td> <td>check property</td>

0.2.0-incubating

29

User Interaction

FitNesse Integration

<td>Approver</td> <td>is</td> <td>Employee:Fred Smith</td> <td>ok</td> </tr> </table>

It is also valid to call inline, ie outside of a table. In this case either isis:execute or concordion:execute can be used; for simplicitly we recommend only ever using isis:execute. For example:
<p isis:execute="#result=usingIsisViewer(#onObject,#aliasResultAs, #perform, #usingMember)"> With the <span concordion:set="#onObject">employees</span> service, <span concordion:set="#perform">invoke action</span> <span concordion:set="#usingMember">All Employees</span> and alias the resulting list as <span concordion:set="#aliasResultAs">list1</span>; <span concordion:assertEquals="#result">ok</span> </p>

5.4. FitNesse Integration


The FitNesse integration provides a Using Isis Viewer (and also Using Isis Viewer For Setup) fixture, to call in table format. For example:
Using Isis Viewer On Object Alias Result As

Perform

alias as

Fred Smith Tom Brown Sam Jones Employee:Fred Smith Employee:Fred Smith

Employee:Fred Smith Employee:Tom Brown Employee:Sam Jones

30

Apache Isis BDD Testing Guide

Chapter 6

Asserting on Collections

Fixtures to assert on the contents of a collection.

Although the user interaction fixtures (in Chapter 5, User Interaction) provide some capability to assert on collections, those collections must belong to an object. It is therefore not possible to use them to assert on the contents of a "free-standing" collection, that is, one that was returned as the result of invoking an action. Those fixtures also do not provide any ability to simply assert on the contents of a collection (whether free-standing or owned by an object). The fixtures in this chapter make it easy to assert on the contents of any collection. For owned collections, there is some duplication with the user interactions fixtures; which you use is up to you.

6.1. Check Collection Contents


These fixtures are used to assert various facts about the contents of a collection. They are typically used in the "Then", though can be helpful as a way of confirming/documenting a "Given".

Common
The common library provides the CheckCollectionContentsPeer that provides the following methods: #isEmpty() returns true if the specified list is empty #isNotEmpty() returns true if the specified list is not empty

31

Asserting on Collections

Concordion

#contains(String) returns true if the specified alias is valid and refers to an object that is contained within the collection #doesNotContain(String) returns true if the specified alias is valid and refers to an object that is not contained within the collection #assertSize(int) returns true if the number of objects in the collection is as specified

Concordion
The Concordion integration provides methods within AbstractIsisConcordionScenario: #checkCollectionIsEmpty() returns true if the specified list is empty #checkCollectionIsNotEmpty() returns true if the specified list is not empty #checkCollectionContains(String) returns true if the specified alias is valid and refers to an object that is contained within the collection #checkCollectionDoesNotContain(String) returns true if the specified alias is valid and refers to an object that is not contained within the collection #checkCollectionSize(int) returns true if the number of objects in the collection is as specified For example:
<p concordion:execute="#result=checkCollectionSize(#tomsClaimsAfterwards,#expectedSize)"> Confirm that tom has <span concordion:set="#expectedSize">2</span> claims; <span concordion:assertEquals="#result">ok</span>. </p>

Each of these simply calls into the corresponding method in the common library.

FitNesse
Not yet implemented.

6.2. Check List


Check items in list, either precisely or just for presence, using their title. Lists are either aliased results of actions, or aliased collections within objects. Note that this ordering does matter (hence CheckList rather than CheckCollection). Typically used in the "Then", though can be helpful as a way of confirming/documenting a "Given". See also AliasItemsInList (Section 6.3, Alias Items In List), which also performs an implicit check (will fail if the objects are not in the list) and aliases them for further use.

Common
The common library provides the CheckListPeer which can be used to check the contents of a table, by title and optionally by type. It is designed to be called in a table format, and so has a constructor that

32

Apache Isis BDD Testing Guide

Asserting on Collections

Concordion

accepts CellBindings to represent the title and type columns. Specifically, the constructor takes the following parameters: AliasRegistry a String for the list alias CheckListPeer.CheckMode the check can be EXACT (the contents of the collection must exactly match those provided in the table) or NOT_EXACT (those objects specified must be within the collection, but there may be additional objects also) CellBinding for title CellBinding for type (optional)

Concordion
The Concordion integration provides a single method #checkList(...): #checkList(String listAlias, String title) If the object is found then the method returns "ok". Calling this method is an assertion that the specified list contains an object with the specified title. It's possible to achieve broadly the same effect using other fixtures (either Section 6.1, Check Collection Contents for both free-standing and owned collections, or the section called Interacting with Collections for owned collections). However, the tabular form afforded by #checkList(...) may make it more appropriate to use in some cases. For example:
<table isis:execute="#result=checkList(#tomsClaimsAfterwards, #title)"> <tr> <th concordion:set="#title">title</th> <th concordion:assertEquals="#result"/> </tr> <tr> <td>New - 2007-2-18</td> <td>ok</td> </tr> <tr> <td>New - 2007-2-14</td> <td>ok</td> </tr> </table>

Note that the Concordion integration only supports only NOT_EXACT mode. An alternative is to use Concordion's own verifyRows(...) mechanism, as described in Section 6.4, VerifyRows (Concordion only).

0.2.0-incubating

33

Asserting on Collections

FitNesse

FitNesse
The FitNesse integration provides two different table fixtures, Check List Contains (corresponding to NOT_EXACT mode) and Check List Precisely Contains (corresponding to EXACT mode). For example:
Check List Contains Title

tomsClaimsAfterwards

New - 2007-2-18 New - 2007-2-14

6.3. Alias Items In List


Closely related to CheckList Section 6.2, Check List, this fixture allows an alias to be associated with items in a list. The list items are located by their title, and are presumed to exist. This fixture can therefore also be used as a way of checking for presence of items in a list.

Common
The common library provides the AliasItemsInListPeer which can be used to check the contents of a table, by title and optionally by type. It is designed to be called in a table format, and so has a constructor that accepts CellBindings to represent the title, the type and alias columns. Specifically, the constructor takes the following parameters: AliasRegistry a String for the list alias CellBinding for title CellBinding for type (optional) CellBinding for alias

Concordion
The Concordion integration provides overloaded versions of #aliasItemsInList(...): #aliasItemsInList(String listAlias, String title, String aliasAs) #aliasItemsInList(String
aliasAs) listAlias, String title, String type, String

If successful, then the found object is aliased to the supplied alias and the method returns "ok". For example, here is how to call the method inline:
<p concordion:execute="#result=aliasItemsInList(#listAlias, #title, #aliasAs)">

34

Apache Isis BDD Testing Guide

Asserting on Collections

FitNesse

Alias <span concordion:set="#title">Tom Brown</span> in <span concordion:set="#listAlias">list1</span> as <span concordion:set="#aliasAs">tomEmployee</span>; <span concordion:assertEquals="#result">ok</span>. </p>

It is also possible to called from within a table.

FitNesse
The FitNesse integration provides the Alias Items In List fixture:
Alias Items In List Title

list1
Alias As

Tom Brown Sam Jones

tomEmployee fredEmployee

6.4. VerifyRows (Concordion only)


Concordion provides its own mechanism for asserting on the contents of a collection, namely concordion:verifyRows. The AbstractIsisConcordionScenario class therefore provides the #getListContents(String listAlias) method that returns the contents of the object (as pojos)

as an Iterable. For example:


<table concordion:verifyRows="#claimPojo: getListContents(#tomsClaimsAfterwards)"> <tr> <th concordion:assertEquals="#claimPojo.description">Description</th> <th concordion:assertEquals="#claimPojo.date">Date</th> </tr> <tr> <td>claim 2</td> <td>2007-2-18</td> </tr> <tr> <td>claim 1</td> <td>2007-2-14</td> </tr> </table>

Note that the value of properties can be asserted using this syntax.

0.2.0-incubating

35

Chapter 7

Debugging

Fixtures for debugging scenarios by inspecting the external or internal state of the Isis system.

There are a number of fixtures available to help you debug your BDD scenarios. Note: if using Concordion, the only fixture currently available is also the most useful, RunViewer (see Section 7.1, Run Viewer).

7.1. Run Viewer


When encountered in the scenario text, this fixture runs the DnD viewer. This is a great way to inspect the state of the system, for example if a test is failing and you can't see why.

Common
This fixture is provided by the Scenario#runViewer() method.

Concordion
The provides this fixture by the AbstractIsisConcordionScenario#runViewer() method. This simply delegates to the common library.
Concordion

integration

For example:

37

Debugging

FitNesse

<p concordion:execute="runViewer()">run viewer</p>

FitNesse
The FitNesse integration provides the Run Viewer fixture, called as a simple 1-cell table:
Run Viewer

7.2. Debugging the Clock


Reads the current value of the clock. Useful for debugging and diagnostics.

Common
The common library provides the DebugClockPeer class.

Concordion
Not yet implemented.

FitNesse
Provided by the Debug Clock fixture.

7.3. Debugging the Object Store


This fixture dumps the contents of the object store. Useful for debugging setup (through SetupObjects, Section 4.5, Setting Up Objects, and UsingIsisViewerForSetup, Chapter 5, User Interaction).

Common
The common library provides the DebugObjectStorePeer class.

Concordion
Not yet implemented.

FitNesse
Provided by the Debug Object Store fixture.

38

Apache Isis BDD Testing Guide

Debugging

Check Specifications Loaded (FitNesse only)

7.4. Check Specifications Loaded (FitNesse only)


Verifies that the listed ObjectSpecifications have been loaded into the metamodel. Provided by the Check Specifications Loaded fixture.

7.5. Debugging Services (FitNesse only)


Lists service class names, as picked up from configuration. Useful with AliasServices (see Section 4.4, Aliasing Services). Provided by the Debugging Services fixture.

0.2.0-incubating

39

Chapter 8

Hints and Tips

Hints, tips and suggestions for writing your own stories and scenarios.

8.1. Structure your scenarios using Given/When/Then


A standard template for organizing structuring tests is given/when/then1: given ... the system is in this particular state when ... this interesting thing happens then ... these are the consequences This structure is readily understood by non-technical business users, and helps them (and the team) focus on the point of the scenario. For example: if using Concordion, use <h2> headers to separate out the different regions of the page. if using FitNesse, use its wiki syntax (eg !1 and !2) to create headers for the different regions of the page; see the FitNesse user guide for more details.

8.2. Use a Story Page to collect together its set of Scenarios


Part of estimating the size of a story includes identifying the acceptance criteria for each of its scenarios. These can be created as children of the story page as placeholders, so that the story page becomes a suite.
1

As first described, I believe, by Dan North in a blog post, Introducing BDD.

41

Hints and Tips

Use a Top-Level Suite Page to Collect a Set of Stories

The child scenarios can be fleshed out as required with plain text during the estimation meeting, and with interactions and assertions (ie actual data) once the iteration starts. For the story page itself, the "as a ... I want ... so that... " template is a good way to summarize the intent of the story. For example: if using Concordion, then the story page can easily reference each of the scenarios using Concordion's concordion:run command. You might also want to have one directory per story, and call this page Index.html. For example:
<h1>New Claim Stories</h1> <ul> <li> <p> <a concordion:run="concordion" href="ScenarioDefaultsOk.html"> new claim defaults ok </a> </p> </li> <li> <p> <a concordion:run="concordion" href="ScenarioOnceCreatedShowsUpForClaimant.html"> new claim shows up for claimant </a> </p> </li> </ul>

if using FitNesse: the !include instruction can be used to list include all referenced scenarios for a story as a "subwiki". the !contents instruction canbe used to create a table-of-contents for these scenarios.

8.3. Use a Top-Level Suite Page to Collect a Set of Stories


In the same way that a story can aggregate scenarios, so can a top-level page aggregate all stories. This can act as a starting point for a whole suite of tests, eg for a single iteration or a single component (more discussion on this in Section 8.6, Separate In-Progress Stories from the Backlog and Section 8.7, Organize Completed Stories by Component). For example, if using Concordion you could adopt the convention that the top level suite page is called "AllStories.html". If so, the Maven surefire plugin could be configured to only run that page:
<plugin> <artifactId>maven-surefire-plugin</artifactId> <configuration> ... <includes> <include>**/AllStories.java</include> </includes>

42

Apache Isis BDD Testing Guide

Hints and Tips

Factor out common "Given"s

</configuration> </plugin>

8.4. Factor out common "Given"s


Just like code, scenarios need to be actively managed, because if the scenarios become hard to maintain, they'll end up being deleted. In fact, we probably should take even more care with the scenarios than the code if they represent the primary documentation of the behaviour of the system. In terms of size, the "given" is far larger than either the "when" or the "then", and therefore this is the area where scenario text can quickly become unmaintainable. So instead, try to factor out your givens into separate pages. For example: if using Concordion, you can use <a href>'s with a concordion:run command to reference another page. if using FitNesse, then use it's !include directive to assemble the pages you need You can do this both within a single story and also across stories; anywhere that there is some degree of commonality. All scenarios require bootstrapping, and many scenarios won't care about the current user or date/time. They may also use much of the same setup of reference data objects. All these scenarios could therefore share a common fixture for all of this setup.

8.5. Use a Declarative Style for Page Names


When factoring out "given"s (see Section 8.4, Factor out common "Given"s), or indeed when writing the "when"s and the "then"s, use a declarative style for the pages. The page should describe what it does, not how it does it. For example, a good page name would be "SetUpCountries". It's clear that this will set up all Country reference data classes. Perhaps even better would be to name the page "AllCountries", because this has a declarative form (explains what is the outcome of the referencing page) rather than imperative (telling the system to set up some Countrys). Such pages can be nested. For example, AllCountries page could be included into an "AllReferenceData" page. For transaction data, we could have a page "JoeBloggsCustomer"; another one again could be "JoeBloggsFiveOrders".

8.6. Separate In-Progress Stories from the Backlog


If you are using an agile methodology then you will be implementing the scenarios for a number of stories per iteration; the remainder will be in a backlog. When you select a scenario for implementation, create

0.2.0-incubating

43

Hints and Tips

Organize Completed Stories by Component

a new page for it in a "CurrentIteration" suite. The objective for the team is therefore to get the entire CurrentIteration suite green. Other stories that you may have identified but not selected for the iteration can remain in a Backlog suite.

8.7. Organize Completed Stories by Component


Once you have completed an iteration and implements all the scenarios of a particular story, move that story out to the relevant component that the story relates to. The scenario tests for stories ultimately are the documentation of the behaviour of the system. A year on you won't remember (and won't care) which iteration you implemented a scenario, you'll be searching for it by the component whose behaviour you want to understand.

8.8. Using the RunViewer fixture


The "given" can often be the hardest part to get setup. To check it, we can use the RunViewer fixture (see Section 7.1, Run Viewer). This will run up the drag-n-drop viewer at the specified point in the test; a visual equivalent of System.out.println(), really. We can therefore take the Given page and add a RunViewer fixture at the end. Note that to do this you must temporarily mark the Given page as a test page.

8.9. Set up Continuous Integration


Since Isis is a Maven application, it is easy enough to configure it to run under a CI server, such as Hudson. You could then use the Hudson HTML Publisher plugin to publish the generated results onto a website. This way they can be easily inspected at any time by your domain experts / business analysts.

44

Apache Isis BDD Testing Guide

Appendix A. Using XmlMind with Concordion


How to use XmlMind to write tests for the Isis/Concordion integration.

XmlMind is an editor for writing structured XML documents, including XHTML documents as used by Concordion. XmlMind is designed to be used by non-technical as well as developers, and provides a WordProcessor-like interface. That said, like any tool it has learning curve. This document describes the most commonly-used features. XmlMind is a Java application, so runs on Windows as well as a number of other operating systems. It can be downloaded from http://xmlmind.com. There are two editions, professional and personal. All the features described in this appendix here are available in the personal edition.

A.1. Customization to support Concordion


XmlMind ships with the DTD for XHTML, along with a corresponding CSS file which is used for the look-n-feel when editing. In order to make XmlMind support Concordion namespaces, we customize both its XHTML DTD and CSS file.Cr Download xhtml1-strict.dtd and copy into addon/config/xhtml/dtd/1.0 (relative to the XmlMind installation directory). This updated version of the DTD defines the concordion and isis namespaces and specifies additional attributes for certain elements Download xtml.css and copy into addon/config/xhtml/css (again, relative to the XmlMind installation directory). This updated version of the CSS highlights those elements on the page that have been annotated using the concordion or isis attributes. The DTD customization specifies the following valid concordion attributes: on any HTML element concordion:assertEquals concordion:set <span> concordion:assertTrue concordion:assertFalse <table> isis:execute

45

Using XmlMind with Concordion

Creating a Document

Use instead of concordion:execute concordion:verifyRows <a> concordion:run These can be specified in the same way as any other attribute.

A.2. Creating a Document


Both Concordion and XmlMind require that the test file is XHTML, meaning that the XML pragma is required. XmlMind also requires that the file specifies the XHTML doctype, and Concordion also requires its namespace to be declared. Use File > New Document to create an XHTML Strict document; this will set up the DTD and namespaces required. Save the file as .xhtml; if running on Windows you can then setup XmlMind is a registered editor for this suffix. Once the file has been created, we recommend that you change the DOCTYPE entry to reference DTDs stored locally. This will substantially speed up the execution time for Concordion, and also allow tests to be run offline (ie not connected to the internet). So, instead of:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> ... </html

edit it to read:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "file:src/test/resources/dtd/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> ... </html

where src/test/resources/dtd is the location of the DTDs. Note that XmlMind doesn't actually read the location of the DTD because it uses the one in its configuration directory (see Section A.1, Customization to support Concordion). So the location should be relative to the base directory of the project (ie the directory where the pom.xml resides).

A.3. Loading a Document


To load an existing XHTML document, use File>Open. This will display the XHTML styled using the CSS.

46

Apache Isis BDD Testing Guide

Using XmlMind with Concordion

Navigating the Document

A.4. Navigating the Document


To navigate around, use up arrow, down arrow, pg up, pg down to move around. It is also possible to navigate by opening up an alternative view to inspect the raw XHTML, using View > Add to bring up a dialog:

The new view is shown in the location specified in the dialog:

0.2.0-incubating

47

Using XmlMind with Concordion

Knowing where you are

This can then be used to navigate, collapsing sections if needed.

A.5. Knowing where you are


The position within the document is shown as a XPath like expression. If the unstyled view is open, then the current position is highlighted (even if the section is collapsed). In the styled view the current cursor position is shown just as in a regular word processor.

48

Apache Isis BDD Testing Guide

Using XmlMind with Concordion

Selecting Content (eg to delete/move, or prior to adding new content)

A.6. Selecting Content (eg to delete/move, or prior to adding new content)


The XMLMind editor understands the structure of XHTML documents, and will only let you enter content where it is valid to do so. What you can do (in terms of edits) therefore depends on where you are in the document. Use Select > Select Parent (ctrl+up) to successively select larger segments of the document; and Select > Select Child (ctrl+down) to selects smaller segments.

0.2.0-incubating

49

Using XmlMind with Concordion

Writing Content

A.7. Writing Content


Adding New Paragraphs
To modify the content in a paragraph, just start writing! Hitting enter will start a new paragraph; delete will join two paragraphs together. Behind the scenes the <p> elements are added.

Adding a new heading (H1, H2) section etc


In general, use Edit > Insert After (ctrl+J) after to add new content after the current location, and Edit > Insert Before (ctrl+H) to insert before. This will bring up a list of valid elements in the top right:

50

Apache Isis BDD Testing Guide

Using XmlMind with Concordion

Adding lists

New sections <h1>, <h2> are valid after any other heading or indeed after a paragraph: <p>. Note that XHTML does not require proper nesting of sections (though it's probably advisable to do so):

Adding lists
Adding lists are added as for any element: use ctrl+J and then select <ul> (unordered list) or <ol> (ordered lists). Youll get the first <li> for free. Create new list items by selecting the current list item (ctrl+up as far as required) then use ctrl+J. If you want to terminate the list, then select the current list (ctrl+up), then ctrl+J and select <p> for next paragraph.

Adding and Altering Tables


Use Edit > Insert After (or Edit > Insert Before), and then select one of the table elements: table table (head_column) to include a header column (on the left hand side) table (head_row) to include a header row table (head_row_column) to include a header row and column For example, head_row:

0.2.0-incubating

51

Using XmlMind with Concordion

Inserting Content to existing Paragraphs

This will generate an initial table:

Then use the XHTML menu to add columns/rows as required:

A.8. Inserting Content to existing Paragraphs


Formatting Existing Paragraphs
In contrast, Edit > Insert (ctrl+I) within a paragraph will only bring up elements that are valid within that paragraph, such as <b> or <em>:

52

Apache Isis BDD Testing Guide

Using XmlMind with Concordion

Formatting Existing Paragraphs

That said, for adding tags within a paragraph (such as emboldening or emphasis), it is generally easier to write the words and then use Edit > Convert (wrap). First, highlight the words by holding shift and then navigating as usual (eg shift+left, shift+right). Then, use Edit>Convert(wrap) to add the emphasis, embolden etc:

resulting in something like:

0.2.0-incubating

53

Using XmlMind with Concordion

Adding Images (and Attributes)

Adding Images (and Attributes)


To add an image, use Edit > Insert and enter an <img> element, resulting in:

With the <img> element now selected, use the attribute view (Tools>Edit Attribute, or ctrl+E) to enter the most important attributes for an <img>: src attribute alt attribute width attribute optional, but recommended

54

Apache Isis BDD Testing Guide

Using XmlMind with Concordion

Deleting Content

A.9. Deleting Content


To delete content, select the content first (ctrl+up / ctrl+down), then Edit > Delete (ctrl+K):

In general you shouldnt need to use the Edit > Force Deletion; instead try adjusting the range being selected if Edit > Delete isnt enabled.

A.10. Moving Content


Select the content you want using ctrl+up. If necessary extend the selection using Select > Extend Selection to Following Sibling or Select > Extend Selection to Preceding Sibling. Use Edit > Cut to cut, and then Edit > Paste After or Edit > Paste Before to paste wherever. If these are greyed out, bear in mind that XmlMind wont let you paste in content where it would be invalid. If necessary, adjust the selection until they become enabled.

A.11. Adding concordion: and isis: attributes


The customization (described in Section A.1, Customization to support Concordion) makes it easy to enter the concordion: and isis: namespaced attributes. For example, to use a <span concordion:set="#someVar"> to set the value of a #someVar variable around, use the following set of steps: select the text to be the value of the variable use Edit>Convert[wrap] and wrap in a <span>

0.2.0-incubating

55

Using XmlMind with Concordion

Adding concordion: and isis: attributes

use Tools>Edit Attribute and select concordion:set attribute

The custom CSS will also highlight those elements that have concordion: or isis: attributes:

56

Apache Isis BDD Testing Guide

Using XmlMind with Concordion

Adding concordion: and isis: attributes

0.2.0-incubating

57

You might also like