Having been a Fitnesse user in the past, I was dissatisfied with the following:
- having interpreted tables with no possibility for automated refactoring
- being limited to tables and not being able to add simple expectations close to the specification text
- being limited to tables while my domain data could be too complex to fit in a tabular format, hence supplementary work when trying to map business concepts to FIT tables
A tool for developers
I first started with the following assumption: most "business users", "subject matter experts" (SME) can't write and maintain FIT specifications. This point of view is not shared by everyone, but my experience shows me that the most workable FIT process is:
- A SME gives examples of what is expected, on the blackboard, on a paper, on an excel spreadsheet, whatever
- A developer walks through the examples with the SME, asking questions, negociating features,... This is the most important part: communicating clearly the specifications through examples.
- A developer writes and implements the examples with the FIT library
- The developer comes back to the SME to clarify hidden assumptions and preconditions as writing things down precisely often shows under-specified parts
- The developer completes the executable specification and refactors it
- The SME validates the final result which should be as readable as possible for her. Possibly, she adds some variations on the first examples, iterating on the whole process
The main conclusion of the small process depicted above is that:
- We can use developer tools because developers are the primary users for the FIT-like specification tools
Of course, this isn't the end of the story. SME are indeed secondary users of those tools and we should take care of them in the future.
But let's focus for now on our primary users. If we create a tool for developers:
- An IDE can be used to edit, refactor and navigate the specifications (think Eclipse, IntelliJ)
- A high-level language can be used to produce them (think Scala of course!)
- The specifications can be stored in a Source configuration and control system (think Subversion, git,...)
Basic specifications
I first started with this vision: a specification should be some free text, explaining what the system is supposed to do. Among that text there are sentences or text fragments which represents examples of what the system should really be doing. I want to be able to "tag" those fragments and "link" them to a piece of code implementing the actions on the system and my expectations.
Free text mixed with executable code = ...? Scala XML literals of course!
Having XML literals in Scala allows to write any kind of text and where necessary, insert some code in curly braces {} to be executed. So my first idea was to allow the developer to:
- write some specification text in a Scala file, with minimal decorum to create a Specification class:
class HelloWorldSpecification extends HtmlSpecification {
"The greeting application" is <textile>
h3. Presentation
This new application should say "hello" in different languages.
</textile>
}
- "tag" part of that text that I consider being an example which should be executed
For example,<ex>by default, saying hello by default should use English</ex>
- add a piece of code implementing the example and setting expectations
For example,<ex>by default, saying hello by default should use English</ex>
{ greet must_== "hello" }
I also liked the idea of using wiki markup to introduce simple formatting features to the Specification. Hence the use of tags showing that the Specification text should be interpreted as Textile marked-up text (the other available markup language is Markdown).
The result of this specification can be seen here. The first example executes without any issue and is highlighted as green text:
For example,by default, saying hello by default should use English
You can note that this constrast a lot with other Specification libraries approaches, like Cucumber, where:
- Text is interpreted and mapped to methods. If I write "Given I have entered 50 in the calculator", there should be a method like I_have_entered_x_in_the_calculator
- The specification flow is imposed through the use of the "Given, when, then" format. My personal feeling is that this is detrimental to writing a good specification where more text, images, tables are needed to explain the expected behavior than just scenarii descriptions
Nesting tables all the way down,...
The first shots at my HtmlSpecifications were encouraging. Simple literate specifications can even be a good way to give examples of an API usage, as in the Mockito specification for specs.
However, I really was missing the table formats I used to work with in Fitnesse.
Then came the next "vision": tables are good, nested tables are better because they can look closer to the data structures we're juggling with all day long. So I should allow tables to be nested and composed of other tables. Hey, even having tabs would be nice, wouldn't it?!
The good news is that nothing else that pure Scala was needed for that.
Let me introduce: Properties, Fields and Forms
Properties
The basic building block of a Form is a Property (of type Prop in specs, Property being reserved for something like this).
A Prop is something which has:
- a label
- an expected value
- an actual value
- a constraint
The most common type of property is declared like this in a Form:
// "Name" is the property label, "Eric" is its actual value
val name = prop("Name", "Eric")
and the expected value can be set with:
name("Bob")
Once executed the property will check for the equality of the actual and expected value, using a beEqualTo matcher. Note that it is possible to change the matcher used to check the values:
val name = prop("Name", "Eric").matchesWith(equalToIgnoringCase(_))
name("eric").execute.isOk // true
Fields
Actually there is an even simpler building block for Forms than a Prop: a Field. A Field is simply a piece of input or output data with a label:
val firstName = field("First name", "Eric") // just a name and value
val tradeId = field("Trade id", trade.getId) // retrieved from the database
Forms
Armed with Props and Fields, building a Form is plain easy:
- declare the props and fields of the form
- declare how they should be layed out
class Person extends Form {
val firstName = prop("First name", "Eric")
val lastName = prop("Last name", "Torreborre")
tr(firstName)
tr(lastName)
}
In the Person form above, we decided that the properties should be displayed on 2 different rows but they might as well be displayed on the same row:
tr(firstName, lastName)
And this can get arbitrarily more complex because you add also forms in a row:
tr(firstName, lastName, address) // address is a Form representing the address
Tabs are also very easy to use when there is more data to organize:
class ClubMember extends Form {
new tabs() {
new tab("Contact details") {
tr(field("First name", "Eric"))
tr(field("Last name", "Torreborre"))
}
new tab("Sports") {
th2("Sport", "Years of practice")
tr(field("Squash", 10))
tr(field("Tennis", 5))
tr(field("Windsurf", 2))
}
}
}
Special forms
As usual, as few patterns emerge when you start using something more frequently. For example, it is pretty common to display data in straightforward tables with column headers. In that case, each cell should be a property with no label and the properties labels should be used to create the header row. The TableForm serves exactly that purpose:
class PayLine(vDate: String, p: Double, r: Double) extends LineForm {
val valueDate = field("Value date", vDate)
val pay = prop("NPV_PAY_NOTIONAL", pricePay(valueDate))(p)
val rec = prop("NPV_REC_NOTIONAL", priceRec(valueDate))(r)
}
new TableForm {
tr(PayLine("6/1/2007", -1732.34, 0.0))
tr(PayLine("4/30/2008", -580332.88, 0.0))
}.report
In a TableForm, each row is a LineForm whose properties and fields will not have their labels displayed inside the table, but which will be used to build the table header. There is an even more cryptic (at least to the eyes of some,...) way of creating such a table using a DataTable, the DataTableForm:
new TradePrice {
"Value date" | "NPV_PAY_NOTIONAL" | "NPV_REC_NOTIONAL" |
"6/1/2007" ! -1732.34 ! 0.0 |
"4/30/2008" ! -580332.88 ! 0.0 | { (vDate: String, pay: Double, rec: Double) =>
tr(vDate, prop(pricePay(vDate))(pay), prop(priceRec(vDate))(rec))
}
}.report
In that example, the properties are created on the fly, without any label at all, because the table header will be directly taken from the DataTable header.
BagForm
This is certainly the most advanced table featured at the moment. In many situations, each row of the table maps to an entity of the domain (let's say a "Customer").
The BagForm allows to specify that a Seq of actual entities should match the expected one declared on each row:
case class CustomerLine(name: String, age: Int) extends EntityLineForm[Customer] {
// the prop method accepts a function here, taking the proper attribute on the "Entity"
prop("Name", (_:Customer).getName)(name)
prop("Age", (_:Customer).getAge)(age)
}
class Customers(actualCustomers: Seq[Customer]) extends BagForm(actualCustomers)
// usage example
new Customers(customersFromTheDatabase) {
tr(CustomerLine("Eric", 36))
tr(CustomerLine("Bob", 27))
}
Each Customer EntityLineForm declares some properties whose actual values are "extracted" from an actual entity.
When the table is constructed (see "usage example"), each line is tested against the actual entities passed as parameter (customersFromTheDatabase), then only the best matches are kept to create the final table, based on the number of successful properties on each line. And if some expected rows are not matched by any actual entity, they are added at the end of the table as failures. A picture is worth 1000 words, the result is here.
Conclusion
The paint on LiterateSpecification and Form is still fresh! I'm promoting it as "Alpha" for the next specs release (1.5.0), because I expect a few bugs to crop in here and there and lots of additional features to be necessary to support "industrial" specifications.
In any case, I expect that the vision I had and the support of the Scala language will allow everyone to create beautiful specifications with lots of examples to discuss with Subject Matter Experts.
Try it, send me all your feedback, I hope you'll like it, I sure do!