Stories offer a powerful mechanism for conveying information– indeed, if written correctly and combined with an adequate means of validation, stories can become executable documentation. For instance, easyb (a behavior driven development framework for the Java platform) supports a story format that consists of:
- given some condition
- when something happens
- then something else should happen (this may or may not necessarily provide validation)
For instance, easyb supports the notion of plug-ins, which are modules that provide additional functionality– currently there is a plug-in that supports database management so that during the context of a story, a database can be seeded. This is, of course, useful should you need to validate high level functionality.
Using easyb itself, it is rather easy to describe what the plug-in does and how to use it. A story to validate the database plug-in sounds a bit like this:
- given that database has an initalized data model (i.e. tables to hold stuff)
- and given the plug-in’s method is invoked with a dataset (i.e. rows for tables)
- when a select statement is issued for a value only found in the dataset inserted by the plug-in
- then the desired item (i.e. column value) should be returned
In essence, this story is the use case for the plug-in– reading it you can most likely ascertain that the plug-in puts rows in database tables. Reading this story, it is also evident that at some point, it may be helpful to start and stop a database instance during the course of the story– i.e. start it up at the commencement of the story and stop it at the end.
Using easyb’s story format, I can sketch out the body of the story as follows (note, the story includes starting and stopping a database):
given "a database is up and running", {}
and
given "the database has an initalized data model", {}
and
given "the database_model method is invoked with a dataset", {}
when "a select statement is issued for a value that could
only be present if the database_model call worked", {}
then "the word bellicose should be returned", {}
and
then "shut down the database", {}
Making the story executable entails adding some logic– in the course of doing so, the story demonstrates how to use the database plug-in (minus the first and last steps, right?)!
The first given can be coded as follows (in Groovy, that is):
given "a database is up and running", {
Server.main(["-no_system_exit", "true"] as String[])
}
The Server instance in the above code is HSQLDB’s org.hsqldb.Server type. This is, by the way, a handy database that is easily controlled programatically and easy to install (it’s a jar file!).
The next given is a bit more complicated– the database needs an empty schema– one can’t assume one is present, so I’ll just create it. This also happens to validate the story quite nicely– no assumptions are made about the underlying database!
and
given "the database has an initalized data model", {
sql = Sql.newInstance("jdbc:hsqldb:hsql://127.0.0.1",
"sa", "", "org.hsqldb.jdbcDriver")
ddl = """DROP TABLE definition IF EXISTS;
DROP TABLE synonym IF EXISTS;
DROP TABLE word IF EXISTS;
CREATE TABLE word (
WORD_ID bigint default '0' NOT NULL,
PART_OF_SPEECH varchar(100) default '' NOT NULL,
SPELLING varchar(100) default '' NOT NULL,
PRIMARY KEY (WORD_ID),
UNIQUE (SPELLING));
CREATE TABLE definition (
DEFINITION_ID bigint default '0' NOT NULL,
DEFINITION varchar(500) NOT NULL,
WORD_ID bigint default '0' NOT NULL,
EXAMPLE_SENTENCE varchar(1000),
FOREIGN KEY (WORD_ID) REFERENCES word(WORD_ID)
ON DELETE CASCADE
ON UPDATE CASCADE,
PRIMARY KEY (DEFINITION_ID));
CREATE TABLE synonym (
SYNONYM_ID bigint default '0' NOT NULL ,
WORD_ID bigint default '0' NOT NULL ,
SPELLING varchar(100) default '' NOT NULL ,
FOREIGN KEY (WORD_ID) REFERENCES word(WORD_ID)
ON DELETE CASCADE
ON UPDATE CASCADE,
PRIMARY KEY (SYNONYM_ID));
commit;"""
sql.execute(ddl);
}
The data model here is quite simple– three tables and I’m using GroovySQL to jam it into the database.
Next, I need to actually use the plug-in to seed the database with data! The core API of the plug-in is the database_model closure, which takes standard JDBC connection information (URL, user name, driver, etc) and a String representing the dataset. In this case, I’m using Groovy’s MarkupBuilder to to create an XML representation of the data (which the underlying code requires– that code uses Java’s own DbUnit, by the way).
and
given "the database_model method is invoked with a dataset", {
database_model("org.hsqldb.jdbcDriver",
"jdbc:hsqldb:hsql://127.0.0.1", "sa", ""){
def writer = new StringWriter();
def builder = new MarkupBuilder(writer);
builder.dataset(){
word(word_id:1, spelling:"bellicose", part_of_speech:"Adjective")
definition(definition_id:10, definition:"demonstrating willingness and willingness to fight ",
word_id:1, example_sentence:"The pugnacious youth had no friends left to pick on." )
synonym(synonym_id:20, word_id:1, spelling:"belligerent")
synonym(synonym_id:21, word_id:1, spelling:"aggressive")
}
return writer.toString()
}
}
Note, the and clauses– in truth they’re noops but they serve to make the story read more nicely. Notice how the MarkupBuilder instance creates data that matches the data model found in the 2nd given. For instance, the word “bellicose” is inserted with an id of 1.
That right there is the heart of the plug-in– inserting data into the database so that a high level story can play out. Now, using easyb, it’s time to validate that the call to database_model worked.
Validating the database_model call means querying the database for the data that the database_model call should have inserted– in my case, I’ll see if the word bellicose is present!
when "a select statement is issued for a value that could
only be present if the database_model call worked", {
value = null
sql.eachRow("select word.spelling from word where word.word_id = 1"){
value = it.spelling
}
}
Note how I’m still using GroovySQL to talk to the database. Once I have the word (stored in the value variable), it’s time to verify it is there.
then "the word bellicose should be returned", {
value.shouldBe "bellicose"
}
Note how easyb supports some magic validation via the shouldBe call– easyb supports a few variations, which ever one you pick is up to you– shouldBeEqual or shouldBeEqualTo or shouldEqual all do the same thing.
Lastly, stopping the database is as easy as sending a shutdown command (via SQL) to the instance of HSQLDB.
and
then "shut down the database", {
sql.execute("SHUTDOWN")
}
The story behind the plug-in’s behavior doesn’t really require a few documents– it just requires one– the story itself, which as you can see has been implemented in easyb (by way of Groovy, but you can certainly run these in Java). easyb is all about easy– the plug-in was rather easy to describe, don’t you think?