Professional Documents
Culture Documents
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
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
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.
Introduction
Common Library
Introduction
Concordion Integration
0.2.0-incubating
Introduction
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.
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.
0.2.0-incubating
Chapter 2
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
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).
Chapter 3
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.
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).
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
Common
The
Scenario#bootstrapIsis(String configDirectory, DeploymentType
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
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
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
FitNesse
12
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).
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
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
role list
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).
14
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>
FitNesse
Not implemented at this time.
0.2.0-incubating
15
Scenario Set Up
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
16
Scenario Set Up
Aliasing Services
Date Is Now
Time Is
Time Is Now
In each case the date/time provided is parsed against the format 'dd MMM yyyy hh:mm'
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
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
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
20
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
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.
24
User Interaction
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
not
valid
not
property check clear property check clear property set property clear property get property
is not valid
object alias
property name
object alias
property name
0.2.0-incubating
25
User Interaction
Obtaining a alias for the (value of) a property only makes sense if the property is a reference type, not value type.
26
User Interaction
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
not
valid
object alias
check to
add
is
not
object alias
valid for
collection
object alias
is for
valid
object alias
object alias
is
not
object alias
valid for
add
to
0.2.0-incubating
27
User Interaction
Obtaining a reference to a collection allows objects to be aliased from within it, using Section 6.3, Alias Items In List.
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
not
valid for
object alias
get
action
action name
object alias
action name
parameter choices
Concordion
framework
methods
in
String
perform,
28
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
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>
Perform
alias as
Fred Smith Tom Brown Sam Jones Employee:Fred Smith Employee:Fred Smith
30
Chapter 6
Asserting on Collections
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.
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.
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
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
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
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>
FitNesse
The FitNesse integration provides the Alias Items In List fixture:
Alias Items In List Title
list1
Alias As
tomEmployee fredEmployee
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).
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
FitNesse
The FitNesse integration provides the Run Viewer fixture, called as a simple 1-cell table:
Run Viewer
Common
The common library provides the DebugClockPeer class.
Concordion
Not yet implemented.
FitNesse
Provided by the Debug Clock fixture.
Common
The common library provides the DebugObjectStorePeer class.
Concordion
Not yet implemented.
FitNesse
Provided by the Debug Object Store fixture.
38
Debugging
0.2.0-incubating
39
Chapter 8
Hints, tips and suggestions for writing your own stories and scenarios.
41
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.
42
</configuration> </plugin>
0.2.0-incubating
43
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.
44
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.
45
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.
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).
46
0.2.0-incubating
47
48
0.2.0-incubating
49
Writing Content
50
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.
0.2.0-incubating
51
52
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:
0.2.0-incubating
53
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
Deleting Content
In general you shouldnt need to use the Edit > Force Deletion; instead try adjusting the range being selected if Edit > Delete isnt enabled.
0.2.0-incubating
55
The custom CSS will also highlight those elements that have concordion: or isis: attributes:
56
0.2.0-incubating
57