Return-Path: Delivered-To: apmail-incubator-isis-commits-archive@minotaur.apache.org Received: (qmail 24279 invoked from network); 10 Dec 2010 16:26:25 -0000 Received: from unknown (HELO mail.apache.org) (140.211.11.3) by 140.211.11.9 with SMTP; 10 Dec 2010 16:26:25 -0000 Received: (qmail 2126 invoked by uid 500); 10 Dec 2010 16:26:25 -0000 Delivered-To: apmail-incubator-isis-commits-archive@incubator.apache.org Received: (qmail 2102 invoked by uid 500); 10 Dec 2010 16:26:25 -0000 Mailing-List: contact isis-commits-help@incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: isis-dev@incubator.apache.org Delivered-To: mailing list isis-commits@incubator.apache.org Received: (qmail 2094 invoked by uid 99); 10 Dec 2010 16:26:25 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 10 Dec 2010 16:26:24 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=10.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 10 Dec 2010 16:26:16 +0000 Received: by eris.apache.org (Postfix, from userid 65534) id 94B3323889B3; Fri, 10 Dec 2010 16:25:53 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1044420 [2/2] - in /incubator/isis/trunk: support/prototype/viewer-bdd/ support/prototype/viewer-bdd/src/test/java/org/apache/isis/support/prototype/scenarios/ support/prototype/viewer-bdd/src/test/java/org/apache/isis/support/prototype/sc... Date: Fri, 10 Dec 2010 16:25:53 -0000 To: isis-commits@incubator.apache.org From: danhaywood@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20101210162553.94B3323889B3@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Modified: incubator/isis/trunk/viewer/bdd/src/docbkx/guide/isis-bdd-viewer.xml URL: http://svn.apache.org/viewvc/incubator/isis/trunk/viewer/bdd/src/docbkx/guide/isis-bdd-viewer.xml?rev=1044420&r1=1044419&r2=1044420&view=diff ============================================================================== --- incubator/isis/trunk/viewer/bdd/src/docbkx/guide/isis-bdd-viewer.xml (original) +++ incubator/isis/trunk/viewer/bdd/src/docbkx/guide/isis-bdd-viewer.xml Fri Dec 10 16:25:52 2010 @@ -691,7 +691,7 @@ ignored (the BDD integration requires that any objects are created through the test scripts, see and ). + linkend="chp.UserInteraction" />). @@ -995,18 +995,15 @@ and running in <span concordion:set=" - + Date and Time Format - BDD tests often rely on an exact date and time, - and the date/time that a scenario is being run upon can be specified - using fixtures (see ). - - However, this date will be specified in text form, as will any - assertions on objects that relate to date (eg dispatch date of an order - within 28 days of it being placed). In order that tests do not fail when - run with different locales, the common library allows a date/time format - to be specified. + 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. Common @@ -1070,6 +1067,10 @@ and running in <span concordion:set=" + 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. @@ -1084,20 +1085,31 @@ and running in <span concordion:set=" Setting Date and Time - Sets the scenario's clock to a specific date and time. Scenarios - that deal with domain objects that use the clock (eg for the defaulting - of a date/time property) will typically need to call this - fixture. - - Typically called within the "given", though may be called many - times throughout the scenario. + 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 - Scenario#dateAndTimeIs(java.util.Date) + 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 ). - This fixture installs the FixtureClock as + 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. @@ -1112,8 +1124,8 @@ and running in <span concordion:set=" Concordion - The Concordion integration provides a number of overloaded - methods, all designed to be called from the + The Concordion integration provides a + number of overloaded methods, all designed to be called from the XHTML: @@ -1130,12 +1142,13 @@ and running in <span concordion:set=" 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> + <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. In all - case the date/time provided is parsed against the format 'dd MMM yyyy - hh:mm' + scenario will want to emphasis the date, other times the time. @@ -1241,8 +1254,8 @@ and running in <span concordion:set=" Concordion - The Concordion integration provides a corresponding method, - #aliasService(aliasAs, String + 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)"> @@ -1267,8 +1280,9 @@ and running in <span concordion:set=" FitNesse - The FitNesse integration provides an implementation of a - ColumnFixture, which is used as follows: + The FitNesse integration provides an + implementation of a ColumnFixture, which is + used as follows: @@ -1302,13 +1316,25 @@ and running in <span concordion:set=" Setting Up Objects - Creates objects, and persists them to the object store. . - Typically used for immutable reference/standing data objects). Can also - be to setup used for transaction/operational data objects (though + 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, , is preferable). The DebugObjectStore - fixture () can be used to check - the state of objects created. + linkend="chp.UserInteraction" />, is generallly to be preferred). + + The DebugObjectStore fixture () can be used to check the state of + objects created. You can also use the RunViewer fixture () to visually inspect the state of the system + using the DnD viewer. Common @@ -1359,14 +1385,14 @@ and running in <span concordion:set=" 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. + 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: - SetUpObjectsPeer#definePropertyOrAlias(String + #definePropertyOrAlias(String propertyNameOrAlias, int colNum) This associates each column with a property of the class, @@ -1379,7 +1405,7 @@ and running in <span concordion:set=" - SetUpObjectsPeer#addPropertyValueOrAlias(String + #addPropertyValueOrAlias(String propertyOrAliasValue) This provides the value of each property of the object to be @@ -1390,7 +1416,7 @@ and running in <span concordion:set=" - SetUpObjectsPeer#createObject() + #createObject() This actually instantiates the object, either persistent or non-persistent as specified in the constructor, and assigns it an @@ -1399,13 +1425,15 @@ and running in <span concordion:set=" That said, there are some other public methods that are - available for more complex integrations (notably: FitNesse). + available for more complex integrations (notably: + FitNesse). Concordion - The Concordion framework integration provides: + The Concordion framework integration + provides: @@ -1436,10 +1464,16 @@ and running in <span concordion:set=" Note that this method should be called from the - XHTML using isis:execute, not - with concordion:execute. See for - details. + 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 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> @@ -1469,13 +1503,17 @@ and running in <span concordion:set=" <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: + The FitNesse integration uses the "Set Up + Objects" table, called like so: @@ -1535,50 +1573,51 @@ and running in <span concordion:set=" User Interaction - *** + Fixtures to describe interactions with the domain objects, + mimicking the way in which an end-user using an Isis viewer would + interact. - User interaction fixtures appear predominantly in the main body of - the test, either to simulating user interactions or to assert on the - results of those actions. - - - Using Isis Viewer / Using Isis Viewer For Setup - - The centrepiece of the BDD framework, simulates - interacting with domain objects as if through a viewer. Using this - feature, the scenario can interact with objects, check their state, - alias referenced or returned objects. - - The "For Setup" version 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 () can be used to check the state of - objects created. + 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 () can be used to check the state of + objects created. - - Common + + 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 assertion on that - collection using "Check List" (see ) - or alias an object out of that list using "Alias Items In List" (see - ). + 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 assertion on that + collection using "Check List" (see ) or + alias an object out of that list using "Alias Items In List" (see ). + + + Constructor Because UsingIsisViewerPeer is table-oriented, it uses CellBindings (see + + + 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 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 result as" column (can also use "result=", + "alias=", "alias as") - The alias to assign the result of any interaction. - + The alias to assign the result of any interaction. + - - the "perform" column (can also use "do", "interaction", - "interaction type") + + the "perform" column (can also use "do", "interaction", + "interaction type") - the interaction to perform; discussed further below - + the interaction to perform; discussed further below + - - the "on member" column (can also use "member", "using - member", using") + + the "on member" column (can also use "member", "using + member", using") - the property, collection or action to use - + the property, collection or action to use + - - the "that it" column (can also use "that", "verb") + + the "that it" column (can also use "that", "verb") - optional qualifier for interactions that make checks; - discussed below - + optional qualifier for interactions that make checks; + discussed below + - - the "with arguments" (can also "arguments", "parameters", - "with parameters", "for", "value", "for parameters", "value", - "reference") + + the "with arguments" (can also "arguments", "parameters", + "with parameters", "for", "value", "for parameters", "value", + "reference") + + 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 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 (). + + + + 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: - 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 - - - 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)" + - 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 () 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. + + + + Validate + + Once the values for the current row have been captured, they can + be validated. The UsingIsisViewerPeer class + provides the following methods for this: + - get property / set property / clear property + #validateOnObject(): + ObjectAdapter - Read from or write to a collection. If setting, a single - argument is required + Verifies that the current value of the "on object" binding + corresponds to a known alias - get collection / add to collection / remove from - collection + #validateAliasAs(): String - Read or write from a collection. If writing, a single - argument is required + Verifies that the current value of the "alias as" binding is + not already in use - invoke action + #validateOnMember(): + ObjectMember - Invoke action, with 0 to many arguments + 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) - get property default / get property choices / get action - parameter default / get action choices + #validatePerform(): Perform - To enable the testing of the choicesXxx() and defaultXxx() - supporting methods + Verifies that the current value of the "perform" binding + corresponds to a known interaction type for the particular type of + member. - The tables below summarizes the full of interactions that are - supported for properties: + Again, see the sections below () 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. + + + + + 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: Supported Interactions for Properties @@ -1961,8 +2128,13 @@ and running in <span concordion:set=" Obtaining a alias for the (value of) a property only makes sense if the property is a reference type, not value type. + - We likewise have a table for collections: + + Interacting with Collections + + The valid values for the various bindings when interacting with + a collection are summarized below:
Supported Interactions for Collections @@ -2200,8 +2372,13 @@ and running in <span concordion:set=" Obtaining a reference to a collection allows objects to be aliased from within it, using . + - Finally, we have a table for actions: + + Interacting with Actions + + The valid values for the various bindings when interacting with + an action are summarized below:
Supported Interactions for Actions @@ -2357,161 +2534,81 @@ and running in <span concordion:set="
- - 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. - - - - To actually perform, these interaction, the set - - - - *** discuss captureCurrent() - - - - the UsingIsisViewerPeer provides the - following methods: - - - - #validateOnObject(): - ObjectAdapter - - - - - - #validateAliasAs(): String - - - - - - #validateOnMember(): - ObjectMember - - - - - - #validatePerform(): Perform - - - - - - #performCommand(ObjectAdapter onObject, String - aliasAs, ObjectMember onMember, Perform perform, - List<ScenarioCell> args) - - - - - -
+
- - Concordion - - The Concordion framework integration - provides a set of overloaded methods in - AbstractIsisConcordionScenario which call into - the UsingIsisViewerPeer: - - - - #usingIsisViewer(String onObject, String - aliasResultAs, String perform, String usingMember) - - - 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. - + + Concordion Integration - - #usingIsisViewerThatArgs(String onObject, String - aliasResultAs, String perform, String usingMember, String arg0, - String arg1, ...) + The Concordion framework integration provides + a set of overloaded methods in + AbstractIsisConcordionScenario which call into + the UsingIsisViewerPeer: - For interactions that require a "that it" and also an - argumetn or arguments (eg "check action placeOrder is not valid - for arg1, arg2, arg3) + + + #usingIsisViewer(String onObject, String + aliasResultAs, String perform, String usingMember) + - There are multiple overloaded versions of this method taking - from 1 to 5 arguments. - - + For interactions that have no "that it" or arguments (eg "get + collectoin recentlyPlacedOrders") + - 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. + + #usingIsisViewerThat(String onObject, String + aliasResultAs, String perform, String usingMember, String + thatIt) - In all cases these methods return the string "ok", or return the - text of an exception otherwise. This makes them easy to embed + For interactions that require a "that it" but no arguments + (eg, "check property firstName that it is hidden") + - Returns ok + + #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"). - Note that this method should be called from the - XHTML using isis:execute, not - with concordion:execute. See for - details. + There are multiple overloaded versions of this method taking + from 1 to 5 arguments. + - For example, calling inline: + + #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) - <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> + 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). - The method can also be called within a table: + For example: - <table + <table isis:execute="#result=usingIsisViewerThatArgs(#onObject, #aliasResultAs, #perform, #onMember, #thatIt, #value)"> <tr> <th concordion:set="#onObject">on object</th> @@ -2533,98 +2630,107 @@ and running in <span concordion:set=" </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: - - FitNesse + <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> +
- + + 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 + - + + + Using Isis Viewer - + - + - - + - - On Object + + - Alias Result As + + On Object - Perform + Alias Result As - + Perform - alias as - + - - Fred Smith + alias as + - + + Fred Smith - + - + - Employee:Fred Smith - + - - Tom Brown + Employee:Fred Smith + - Employee:Fred Smith + + Tom Brown - + Employee:Fred Smith - + - Employee:Tom Brown - + - - Sam Jones + Employee:Tom Brown + - Employee:Fred Smith + + Sam Jones - + Employee:Fred Smith - + - Employee:Sam Jones - - - - + - -
+ Employee:Sam Jones + + + +
@@ -2632,86 +2738,114 @@ and running in <span concordion:set=" Asserting on Collections - *** + Fixtures to assert on the contents of a collection. - + Although the user interaction fixtures (in ) 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. - - Check List Is Empty / Check List Is Not Empty + + 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 + - - Concordion + + #isNotEmpty() returns true if the + specified list is not empty + - + + #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 + + - FitNesse - - Not yet implemented. - - + Concordion - - Check List Contains / Check List Does Not Contain + The Concordion integration provides + methods within + AbstractIsisConcordionScenario: - - - - Common - - - - - - - - Concordion - - - - - - - - FitNesse - - Not yet implemented. - - - - - Check List Size + + + #checkCollectionIsEmpty() returns + true if the specified list is empty + - + + #checkCollectionIsNotEmpty() + returns true if the specified list is not empty + - - Common + + #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 + + - - Concordion + 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. @@ -2726,7 +2860,9 @@ and running in <span concordion:set=" 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. + 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". @@ -2736,111 +2872,358 @@ and running in <span concordion:set=" 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 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 for both free-standing and + owned collections, or 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 + . 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 + + tomsClaimsAfterwards + + + + Title + - + + New - + 2007-2-18 + + + + New - + 2007-2-14 + + + + Alias Items In List - 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 (similar to CheckList, ). + Closely related to CheckList , 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. - Typically used both in the "Given" (to simplify writing the rest - of a test). + + 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 + - - Common + + 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 listAlias, String + title, String type, String aliasAs) + + + + 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)"> + 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 + + list1 + + + + Title + + Alias As + + + + Tom Brown + + tomEmployee + + + + Sam Jones - + fredEmployee + + + + - Concordion's VerifyRows (Concordion only) + 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. Debugging - Debugging and diagnostics. Useful for checking setup, for - example. + + 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. - - Debugging Services + Note: - Lists service class names, as picked up from configuration. Useful - with AliasServices (see ). + + + if using Concordion, the only fixture + currently available is also the most useful, RunViewer (see ). + + + + + 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 Concordion integration provides + this fixture by the + AbstractIsisConcordionScenario#runViewer() + method. This simply delegates to the common library. + + For example: + + <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 + + + + @@ -2853,98 +3236,73 @@ and running in <span concordion:set=" Common - + The common library provides the + DebugClockPeer class. Concordion - + Not yet implemented. FitNesse - + Provided by the Debug Clock + fixture. Debugging the Object Store - Dumps the contents of the object store. Useful for debugging setup - (through SetupObjects, , and - UsingNakedObjectsViewerForSetup, ). + This fixture dumps the contents of the object store. Useful for + debugging setup (through SetupObjects, , and + UsingIsisViewerForSetup, ). Common - + The common library provides the + DebugObjectStorePeer class. Concordion - + Not yet implemented. FitNesse - + Provided by the Debug Object Store + fixture. - Check Specifications Loaded - - Verifies that listed ObjectSpecifications - have been loaded into the metamodel + Check Specifications Loaded (FitNesse only) - - Common + Verifies that the listed + ObjectSpecifications have been loaded into the + metamodel. - - - - - Concordion - - - - - - FitNesse - - - + Provided by the Check Specifications Loaded + fixture. - - Run Viewer - - Runs up the DnD viewer with the current state of the objects. 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 - - - - - - Concordion - - - + + Debugging Services (FitNesse only) - - FitNesse + Lists service class names, as picked up from configuration. Useful + with AliasServices (see ). - - + Provided by the Debugging Services + fixture. @@ -2952,95 +3310,12 @@ and running in <span concordion:set=" Hints and Tips - This chapter contains a collection of hints, tips and suggestions - for writing your own tests. + Hints, tips and suggestions for writing your own stories and + scenarios. - For further guidance, we recommend that you check out Gojko Adzic's - book, Bridging - the Communication Gap. - - 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 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. - - - - Use a Story Page to Collect A Set of Scenario Tests - - 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. The child scenario tests can be fleshed out as required - with plain text during the estimation meeting, and with actual tests - 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. - - - Concordion - - Use the <a href="ScenarioHappyCase.html" - concordion:run="concordion">happy case</a> - - Use the <a href="ScenarioNoFundsAvailable.html" - concordion:run="concordion">no funds available</a> - - - - - - FitNesse - - The FitNesse !contents - instruction will then list all the acceptance criteria for the - story. - - - - - Factor our common fixtures - - Fixtures.html (group of scenarios) - - - - - - - - Use a Top-Level Suite Page to Collect a Set of Stories - - - - - - 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. - - - - Structure your test using Given/When/Then + Structure your scenarios using Given/When/Then A standard template for organizing structuring tests is given/when/then @@ -3065,59 +3340,156 @@ and running in <span concordion:set=" This structure is readily understood by non-technical business users, and helps them (and the team) focus on the point of the - test. + scenario. - In terms of mechanics, one approach is to put the "given" into the - setup page for a test, with the "when" and the "then" in separate - pages.d + 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. + + - - Using the RunViewer fixture + + Use a Story Page to collect together its set of Scenarios - The "given" can often be the hardest part to get setup. To check - it, we can use the RunViewer fixture (see ). 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. + 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. 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. - Note that to do this you must temporarily mark the Given page as a - test - page. + 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. + + + + + + + + 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 and ). + + 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> + </configuration> +</plugin> Factor out common "Given"s - Just like code, tests need to be actively managed, because if the - tests become hard to maintain, they'll end up being deleted. In fact, we - probably should take even more care with the tests than the code if they - represent the primary documentation of the behaviour of the + 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 tests can quickly - become unmaintainable. So instead, try to factor out your givens into - separate pages. + 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:execute tag to include another page. + if using Concordion, you can use + <a href>'s with a + concordion:run command to reference another + page. - if usingFitNesse, then use it's if using FitNesse, then use it's !include directive to assemble the pages you need - The names of these pages should also follow a declarative style, - see . + 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. @@ -3141,15 +3513,58 @@ and running in <span concordion:set=" "JoeBloggsFiveOrders". + + 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 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. + + + + 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. + + + + 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 ). 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. + + 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. If you google around you - should also be able find a way to make Hudson publish the test results - onto a website so that they can be inspected by your domain experts / - business analysts. + 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.