Agile Renaissance


Using FitNesse to Validate Nested Business Structures

Print the article

This entry was posted on 4/30/2009 10:13 PM and is filed under Development,Coaching.

Last year while working at a client site I helped a team validate the work they were doing on one of the company's products. The product has a business structure that is nested in nature and looks like this:


The E, B, and W structures are similar in nature in that they each have a definition part and then contain one or more nested structures. There can be as many as twenty five B objects nested inside an E object and up to a total of one hundred W objects nested inside the different B structures. The H structure is actually one of over one hundred different types and one of more of them, usually four to eight, are contained in a W structure.

By the way, this nested business structure pre-dates computers and is well understood by the organization's staff.

The team I was working with were developing a new component that performed a core business process using information from all levels of this business structure. The development team were creating and validating their work using unit tests. However they needed to have component level tests as well. These would not only be written by members of the team but needed to also be written and executed by other members of the organization over the lifetime of the component. As the organization already had positive experience using FitNesse, it was decided that it would be used to do the component level testing. The team started by creating tables of test facts for definition values: EDef, BDef, WDef and one of the H structures, h1. Simultaneously, I created the associated fixtures, which were based on ColumnFixture. A typical table would look like this (note that it is proceeded by a table that defines the import tat all subsequent tables will use:

import
com.client.lob.product1.edeffacts

 

edeffacts
val1val2>val3>=create()
12>3>edefval1
45>6>edefval2

Once all of the definition tables had been created, the next step was to assemble a test scenario. To this, required developing intermediate objects, thus creating a multi-step process. These objects are created in reverse order starting with W and then B and then E. The following six tables show this process, assume that the FitNesse symbols wdefval1, bdefval1, bdefval2, h1val1 and h1val2 exist. Construction of a E, B or W object requires two tables: the first to create the object binding in its definition and the second binds in its nested structures. I did this way as opposed to having one table composed of 27 columns, because for most of the tests, most of the columns would have been empty and that bothers me and there was no guarantee that the current limit of 25 would be kept. In the following example, the table creates two W objects, w1 and w2:

w
def==create()
wdefval1w1
wdefval1w2

The next table populates each of these W objects with a H1 object:

w
w=h=add()
w1h1val1true
w2h1val2true

Now to create the two B objects and then populate them with the created W objects:

b
def==create()
bdefval1b1
bdefval1b2

 

b
b=w=add()
b1w1true
b2w2true

Now to create an E object and then populate it with the created B objects:

e
def==create()
edefval1e1

 

e
e=b=add()
e1b1true
e2b2true

Note how in the last table, the e1 object appears in two rows, this how the two B objects are added to it. The result of all of this composition is that the E object, e1, contains two B objects, b1 and b2, and these in turn contain w1 and w2 respectively and these in turn contain h1val1 and h1val2 respectively. With the test scenario object created it is now time to tested that it was processed correctly. The first set of values to be validated occur at the E level and the test table to do this looks like this:

validateElevel
e=process()eLevelVal1()eLevelVal2()eLevelVal3()
e1truee1xxxe1yyye1zzz

The processing also created values at the B and H levels, which also need to validated., The following shows how H level values are validated:

validateHlevel
e=b=w=h=wLevelVal1()wLevelVal2()wLevelVal3()
e1b1w1h1val1e1b1w1xxxe1b1w1yyye1b1w1zzz
e1b2w2h1val2e2b2w1xxxe2b2w1yyye2b2w1zzz

Notice that to test the values computed for the H object values, h1val1 & h1val2, all of the E, B and W level objects they are nested in need to be specified. This is because H level objects can be attached to more then one W object and this full nested specification is the only way to correctly identify the H object whose values are to be validated.

The team successfully used the above mechanism to validate their first external release. However, they found it clumsy to compose test scenarios and when trying to find a test fact when a defect occurred they found it confusing to do the trace back. So they asked if there was a easier way to compose tests, one that did not involve intermediate tables. I said I didn't know, but that first we should figure out an easier way to specify a test scenario. After some dialog we came up with the following:

e2
e1edefval1bdefval1wdefval1h1defval1
e2edefval2bdefval2wdefval2h1defval2h2defval1
e3edefval3bdefval1wdefval1h1defval1h2defval1
+ 
wdefval2h1defval2h2defval2
+ bdefval2wdefval3h1defval3h2defval3h3defval1

If the first cell of a row contains a symbol then it starts the definition of a test scenario, which is then attached to the symbol. In the example table, the first two rows create test scenarios and attached them to the FitNesse symbols e1 and e2. The third test scenario, symbol e3, started in row three is different because of the fourth row which starts with a plus sign (+). The plus sign indicates that the test scenario, e3, continues in this row and in fact it also continues into the fifth row. The plus sign is continuation or concatenation operator and it was introduced so that large test scenarios, some contain two or more B level objects plus multiple W and H level ones, could be formatted in such a way as to make it easier to read. All other cells in the rows contain symbols that reference test facts each of which is to be added to the parent object. The team did not want to have to scroll to the left to read a full test scenario, especially given the fact that columns on the left would go out of view. There are two other points to draw your attention to: empty cells are permitted and are just skipped over. The other point is that the type of object referenced determines how the nesting occurs: subordinate objects, for example a B object after an E, causes a new nested level to be opened; encountering a object of parent type, for example a W object after an H object causes the current nested level to be closed and a peer of its parent to be opened; encountering an ancestor element, for example a B object after an H causes the one of more, in this case two levels of nesting to be closed and new peer at the same ancestor level to be opened. This fluid format allows the team to compose nested test scenarios without having to specify intermediate objects like B or W. The one downside to this approach is that it is not self-describing, like most other fixtures, column headers in ColumnFixture, but this did not turned out to be a problem for two reasons: once the grammar is explained to someone who understands the business object they can easily read and create test scenarios using it and the second the names of the symbols matched the business domain and most people were able to duce the structure from that.

With the desired format specified, the next step was to see if a fixture that supported it could be built. To do this I examined the fixture types supplied with FitNesse and I also looked around on the web to see if there was anything like it and found nothing; so I set out to create one. The base class used by all fixtures is called unsurprisingly Fixture and it is found in the Fit system. Upon examination I found that this class provides all of the functionality needed to create the new fixture and to build it all I had to do was override its public method doTable. Once the new test scenario assembly fixture was created, the team re-wrote their existing tests using it in about 45 minutes, and in doing so eliminating three sets of tables. After making sure that these test still ran correctly, they were ready for more. They started by pointing out that only 1 of the H objects had been implemented and that the remaining 99 were not exactly like the first one as many of them had other test value types. This meant that using the existing approach would require the addition of more columns and that the validation rows would contain many empty cells and this did not appeal to them. They also wanted to use the more fluid approach developed in the test scenario assembly fixture to specify the validation conditions.

Before I describe the solution we came up with, I want to point out that validation can occur at all levels and to do it requires solving three problems:

  1. Specifying the object to be tested. For example. an H object is located in a W object, which is located in a B object which is located within the E object.
  2. Specifying which property of the object is to be validated.
  3. Specifying the expected value.

After another design session with the team we came up with the following solution which merges the validation into the test scenario composition (I have added green and red cell formatting to give you a flavour of what it looks like when the FitNesse test has been executed):

validateE
e1eLevelVal1 == e1xxxeLevelVal2 == e1yyyeLevelVal3 == e1zzz
+bdefval1wdefval1wLevelVal1 == e1b1w1kkk
+  h1defval1hLevelVal1 == e1b1w1h1xxxhLevelVal2 == e1b1w1h1yyyhLevelVal3 == e1b1w1h1zzz
 
e2eLevelVal1 == e2xxxeLevelVal2 == e2yyyeLevelVal3 == e2zzz
+bdefval2wdefval2wLevelVal1 == e2b2w2kkk
+  h1defval2hLevelVal1 == e2b1w1h1xxxhLevelVal2 == e2b1w1h1yyyhLevelVal3 == e2b1w1h1zzz
+  h2defval1hLevelVal1 == e2b1w1h2xxxhLevelVal7 == e2b1w1h2yyy
 
e3eLevelVal1 == e3xxxeLevelVal2 == e3yyyeLevelVal3 == e3zzz
+bdefval1wdefval1wLevelVal1 == e3b1w1kkk
+  h1defval1hLevelVal1 == e3b1w1h1xxxhLevelVal2 == e3b1w1h1yyyhLevelVal3 == e3b1w1h1zzz
+  h2defval1hLevelVal1 == e3b1w1h2xxxhLevelVal7 == e3b1w1h2yyy
+ wdefval2wLevelVal1 == e3b1w2kkk
+  h1defval2hLevelVal1 == e3b1w2h1xxxhLevelVal2 == e3b1w2h1yyyhLevelVal3 == e3b2w1h1zzz
+  h2defval2hLevelVal1 == e3b1w2h1xxxhLevelVal2 == e3b1w2h1yyy
+bdefval2wdefval3wLevelVal1 == e3b2w3kkk
+  h1defval3hLevelVal1 == e3b2w3h1xxxhLevelVal2 == e3b2w3h1yyyhLevelVal3 == e3b2w3h1zzz
+  h2defval3hLevelVal1 == e3b2w3h2xxxhLevelVal7 == e3b2w3h2yyy
+  h3defval1hLevelVal1 == e3b2w3h3xxxhLevelVal8 == e3b2w3h3yyyhLevelVal27 == e3b2w3h3zzz

There is lot going in this table:

  1. the fluid test scenarios composition style was kept in this expanded form.
  2. The first cell of a test scenario, the one containing the symbol to which the test scenario is bound is marked green. This indicates that the processing of the scenario occurred correctly, i.e. connection to the system-under-test was successful, that transmission and reception of messages was successful and that no process execution occurs were flagged. If there has been an error in any of these steps the cell would have been marked red.
  3. Cells that contain the pattern 'string == string' specify an equality test. They are associated with the object that they follow, be it an E, B, W or H one. This association makes it easy to see what property on which object is being validated. The left-hand string is the name of the property associated with the current object, whose value is to be validated. The right-hand string specifies the expected value. Note that when a test fails the received value is also written into the cell so that user can see the difference between the two values. The property string is dynamically evaluated by the fixture and as it turns out this reduces the amount of maintenance programming needed to support the fixture as new H objects and their associated properties can be added to the system-under-test without the need to do any new fixture programming.

This fluid composition style for handling their nested business structure was well received by the team and I thank them for their co-operation and support and for their insistence that there must be a better way to validate the nested business structure; this was an interesting challenge and I like the solution we came up with. So gentle reader, I am interest to hear both how others have tackled this type of problem with FitNesse or with some other testing tool and if this technique is one that others might use or how it can be improved. I would also like to the thank the two developers who helped me implement these fixtures.

 

What did you think of this article?




Trackbacks
Trackback specific URL for this entry
  • No trackbacks exist for this entry.
Comments
    • No comments exist for this entry.
Leave a comment

Submitted comments will be subject to moderation before being displayed.

 Enter the above security code (required)

 Name

 Email (will not be published)

 Website

Your comment is 0 characters limited to 3000 characters.