As folks know, I'm a big advocate of automated testing in general and unit testing in particular. I've gradually become a big fan of Test-Driven Development (TDD) where you write tests first and then write the code to satisfy the tests. I'm pleased to see unit testing well enough established in CFML development now that we have several unit testing frameworks (my current favorite being
MXUnit, which I think has become the de facto standard choice for most CFers who are doing unit testing).
Getting into TDD is not easy, however, and I think there are a couple of conceptual problems that take a while to get your head around. One is just a simple case of "Where do I start?". Given a blank piece of paper, how do you just start writing tests that are an accurate representation of what the yet-to-be-written system is supposed to do?We know how to design systems - we start out with use cases and scenarios (or, if you prefer the more traditional terminology, requirements) and we iteratively map those down into system components and interactions and then down into elements that can actually be implemented. This software design process should also help you recognize the next big problem with trying to adopt TDD: unit testing - and TDD in general - focuses on small, testable pieces of code, i.e., methods and classes.
A couple of years ago, Dan North
introduced the concept of Behavior-Driven Development which was intended to help tackle the disconnect between the low-level focus of TDD and the higher-level focus of requirements. What he wanted to do was to find a way to specify higher-level behaviors in the system in a form that could still be executed as a set of tests.
Since then, a number of frameworks have appeared in a variety of languages to support
Behavior-Driven Development (see the implementation section) including RSpec (for Ruby) and GSpec (Groovy), supplementing existing TDD frameworks and techniques. I'm very pleased to see that Ron Hopper has just
announced cfSpec: Behavior-Driven Development for ColdFusion!
I downloaded a snapshot this morning and started writing behavioral tests pretty much immediately, despite the current lack of documentation (there is a nice directory full of sample tests). cfSpec makes use of two powerful features to allow a rich, descriptive approach to specifying the behavioral tests. The first is one of my favorite ColdFusion 8 features -
onMissingMethod() - which is used to allow behavior specifications to be written in close to natural language:
<cfset a.shouldBeAnInstanceOf("my.Object") />
<cfset b.shouldEqual("expected") />
The second feature is an application of
cfimport that I've always liked but never seen a really good use of before:
prefix="". This allows you to write simple tag-based code without the distracting library identifier, e.g.,
<cfimport taglib="/cfSpec" prefix="" />
<describe hint="Container">
<before>
<cfset list = $( createObject("component","util.List") ) />
</before>
<it should="be empty">
<cfset list.shouldBeEmpty() />
</it>
</describe>
In this specification, the
describe,
before and
it tags are part of cfSpec. Using the empty taglib prefix allows the specification to be very readable.
The behavior under test - it should be empty - runs
onMissingMethod() on the
Expectations object (created by the call to the
$() method) and, because it is "should BeEmpty", it checks the
list is empty, by calling
isEmpty() (because the value underlying the expectation is an object, not a simple value). Essentially, for objects,
shouldBeXyz() maps to a call to
isXyz() on the underlying object (and checks the result is true).
Another way to write the behavior under test would be:
<cfset list.isEmpty().shouldBeTrue() />
Calling
isEmpty() on the
Expectations object in turn calls
isEmpty() on the underlying object and returns an
Expectations object based on the result of
isEmpty() - which is boolean and should be true or false.
2 responses so far ↓
1 salomoko // Jan 2, 2009 at 6:07 PM
been looking forward for someone to have the time to write a BDD DSL for CFML!
2 Ron Hopper // Jan 5, 2009 at 2:00 PM
Leave a Comment