Domain Specific Language specifications of easyb

A Domain Specific Language (or DSL, which is much easier to write and say) is a programming language designed for a specific task-- that is, it's a special purpose syntax. For example, Ant's XML build files are DSLs for building software.

Not surprisingly, easyb makes heavy use of DSLs for defining a behavior focused language-- in this case, easyb actually has two main DSLs-- one for defining stories and another for defining basic RSpec-like behaviors.

Story DSL

Stories in easyb are files which contain scenarios; consequently, by convention, easyb story file names end in .story. For example, if you have a create account story, the file would be CreateAccount.story.

Story files have 0..N scenarios in them. Scenarios then have a mixture of 0..N givens, whens, and thens, with ands linking them (if you desire).

Consequently, the definition of an easyb scenario is:

scenario "text", {
 given "text", {}
 when "text", {}
 then "text", {}
}

The body of any phrase (given, when, then) is optional as well (meaning given "text" without the trailing , {} implementation code is completely valid syntax)-- easyb will mark such an event as a pending one.

Remember, you can have as many givens, whens, and thens, with ands linking them; therefore, the DSL supports

scenario "text",{
 given "text", {}
 and
 given "text", {}
 when "text", {}
 then "text", {}
 and
 then "text", {}
}

The DSL also permits chaining ands -- for instance, after a given, the next and is assumed to also imply given. The same would be true of any when's or then's that had proceeding and's. Accordingly, the following syntax is also valid:

scenario "text",{
 given "text", {}
 and "given text", {}
 when "text", {}
 then "text", {}
 and "then text", {}
}

Don't forget that whens are not required. Accordingly, the following variation works too.

scenario "text", {
 given "text", {}
 then "text", {}
}

Further documenting stories

easyb supports capturing additional information regarding stories, such as a story's description and some detail regarding the features, benefits, and roles of a persona related to a story. For instance, the DSL supports a description syntax that takes a String value -- single quote or Groovy's triple quote trick.

description "some description"
scenario "text"
or
description """some long description that requires
multiple lines, etc
"""
scenario "text"

What's more, you can provide additional details of a story via the narrative syntax:

description "text"

narrative "description", {
	as_a "role"
	i_want "feature"
	so_that "benefit"
}

scenario "text"

Both the narrative and description keywords are optional and they don't have to be used together-- i.e. you can use the narrative one without providing a description. These aspects will be captured in the output (i.e. story report) of an easyb run too.

Running non-implemented stories

easyb permits running stories that are not implemented yet-- thus, you can execute your documentation and see which stories are still pending. For instance, the closure (i.e. {}'s following a scenario or given/when/then phrase) is optional. Thus, the following story with two scenarios could be run:


scenario "this is a pending scenario", {
 given "something"
 when "something is done"
 then "there is a way to handle it"
}

scenario "this is also pending"

Both scenarios will be executed and easyb will consider them pending and thus report that-- in this case, easyb will have run one story with two scenarios, each in a pending state:


Running pending example story (PendingExample.story)
Scenarios run: 2, Failures: 0, Pending: 2, Time Elapsed: 0.444 sec

2 total behaviors run (including 2 pending behaviors) with no failures

Behavior DSL

easyb supports an RSpec-like DSL for defining simple behaviors. Along with supporting it, you can create a fixture using the before syntax.


before "", {}
it "", {}

Of course, you can have as many it's as you'd like. The contract of easyb is that the code in before will be run before each it. If you'd only like a one time setup, then place that logic above the first it and that code will be run once.

easyb should syntax

easyb auto-magically wires all objects within the confines of a story or behavior to respond to a series of should calls. That means you can easily verify the state of things by writing phrases like :


var.shouldNotBe null  
and  
var.length().shouldEqual 6

Currently, easyb supports the following phrases, where the phrase is attached to any object and the phrase takes a value to be verified against.


shouldBe
shouldEqual
shouldBeEqual
shouldBeEqualTo

easyb supports the negative of the above phrases as follows (same rules apply as above):

	
shouldNotBe
shouldNotEqual
shouldntBe
shouldntEqual

What's more, easyb allows you to verify object types, such as value.shouldBeAn Integer. Both positive and negative phrases are supported:


shouldBeA
shouldBeAn
shouldNotBeA
shouldNotBeAn

You can compare values with the should syntax as well:


shouldBeGreaterThan
shouldBeLessThan

Lastly, easyb supports verifying objects in a collection or properties of objects via the shouldHave method.


shouldHave	
shouldNotHave

To see all of the above verifications in action, look at some of the stories and behaviors in easyb's source.

easyb ensure syntax

easyb has an expressive ensure syntax that is similar in nature to Java's assert but a bit more readable.

Whenever you want to verify the state of a particular object, use easyb's ensure closure, which supports the following syntax:


ensure(object or expression){
  expression
}

That is, the ensure closure takes a value, which could be a normal object or an expression itself. For instance, you could ensure that some value was false by writing:


ensure(!value)

You could alternatively write:


ensure(value){
  isFalse
}

As you can see, inside the ensure clause you can do some cool things, such as:


isNull
isNotNull
isA<class type>
isEqualTo(value)
isEqualTo<value>
isNotEqualTo<value>
isTrue
isFalse

You can chain clauses too:


ensure(value){
 isNotNull
 and
 isAString
}

The ensure DSL is quite forgiving-- for instance, check out these code examples:


mVal = "Test"
ensure(mVal){
 isEqualToTest
 and
 isEqualTo "Test"
}

mVal = 23
ensure(mVal){
 isEqualTo23
 and
 isEqualTo 23
}

You can work with collections and even ensure fields on objects too:


ensure("test"){
 contains("est")
}

ensure([1,2,3]){
 contains(3)
 and
 contains([2,3])
}

def person = new Person("Andy", 11)
ensure(person){
 contains(firstName:"Andy")
 and 
 contains(age:11)
}

Flexibility is key, hence you can use has instead of contains if you wish:


def person = new Person("Jill", 11)
 ensure(person){
  has([firstName:"Jill", age:11])
}

You can also check that an exception is thrown using the ensureThrows variant of the ensure closure:


ensureThrows(RuntimeException) {
	throw new RuntimeException("Test")
}