This tutorial is intended to be a gentle introduction to cfSpec and behaviour-driven development (BDD) in general. We will walk bit-by-bit through the installation of cfSpec and the creation of a simple shopping cart component using a BDD approach. I will assume you are familiar with object-oriented programming in ColdFusion.

Installation (requires ColdFusion 8)

You can download the latest version of cfSpec from RIAForge (v0.1.0 as of this writing). Just unzip the contents into your ColdFusion webroot (or in a directory mapped as /cfspec). That’s all there is to it.

If you are familiar with git and feeling adventurous, you can also retrieve the absolute latest code from GitHub. You can clone the repository to your local machine, or fork it on GitHub and help out with the development effort.

You can verify the installation was successful by running one of cfSpec’s own specs. Open a browser and enter this URL:


http://your-coldfusion-server/cfspec/spec/expectationsSpec.cfm

If all is as it should be, you’ll see a lot of green, which means the code is behaving as expected. Near the bottom, you’ll also see some red items (under the heading Failures) which represent code that didn’t do what was expected and even some purple items which represent uncaught exceptions.

Getting Started

For this tutorial, we’re going to start work on a Cart component which could be part of an e-commerce website. Let’s start by creating a project called specdemo in the ColdFusion webroot (or under the mapping /specdemo). For simplicity we’ll put everything willy-nilly into this directory.

The first thing we need to do is create a spec for our cart—we’ll call it cartSpec.cfm and enter the following code:

1
2
3
4
<cfimport taglib="/cfspec" prefix="">

<describe hint="Cart">
</describe>

This is a typical starting point for specs that are written with cfSpec. The first line imports everything you need, then we use cfSpec’s <describe> tag to set the stage for our expectations. It has one attribute, hint, which tells what we’re talking about. In this example, it’s obviously our yet-to-be-developed Cart object.

Now that a spec has been written, it’s already time to run it—we’ll be doing that a lot, so get used to it. To run this spec, make sure to save the file, then pop open a browser, type in the spec’s URL (http://your-coldfusion-server/specdemo/cartSpec.cfm), and see what you get. Here’s my output:

So our spec is up and running, talking about the cart, but rather blandly expects nothing of it. Let’s remedy that now by writing an expectation into the spec.

The First Expectation

Where to begin? Well, since this cart is going to be used on an e-commerce site, the first thing that comes to my mind is the little cart icon in the top right that says whether or not there’s something in the cart. Obviously, to support this, the cart will need to know if it has something in it or not. Let’s plan on a method isEmpty() to tell us the answer. Update cartSpec.cfm as follows:

1
2
3
4
5
6
7
8
9
10
<cfimport taglib="/cfspec" prefix="">

<describe hint="Cart">

  <it should="be empty when first created">
    <cfset cart = createObject("component", "specdemo.Cart")>
    <cfset $(cart).shouldBeEmpty()>
  </it>

</describe>

Now that we’ve added out first expectation, let’s pick it apart. First, in cfSpec, expectations are written inside of <it> tags. An <it> tag uses the attribute should to explain the specific behaviour that is expected. This is where we want to clearly capture a single expected behaviour of the thing we’re describing—the clearer the better. Also keep in mind that this expectation should be in the language of the problem domain, not just in Englishy code. A non-coder who understands the problem domain should be able to read the spec output and make sense of it.

In line 6, we’re just creating an instance of the Cart object, and line 7 is where it get’s magical. First the $() function is used to wrap the new Cart object. This is an expectations wrapper, and let’s us use a whole slew of methods that start with should to explain in code exactly what the expected behaviour of the object under scrutiny should be. In this case, we say it shouldBeEmpty(). The BeEmpty portion is our expectation matcher. For a complete list of currently supported expectation matchers, see the development wiki. The BeEmpty matcher that we are using can check for an empty string, array, struct, etc. For an object, it will translate this into an isEmpty() call. This is true for BeAlmostAnything. So if we have written $(cart).shouldBeAmazing(), cfSpec would end up asking the yes/no question isAmazing().

Enough talk, let’s run our updated spec. Here’s my output:

Purple means something blew up while checking the expectation—we’re looking for green. Naturally, we knew that cfSpec wasn’t going to write the code for us, so this is no surprise. Let’s create the actual Cart component, now that we have an expectation telling us we need one. Moving along in tiny steps, here’s the starter code for Cart.cfc:


<cfcomponent></cfcomponent>

Now, let’s run the spec again to see what it says…

Still purple, but now it’s complaining about the lack of an isEmpty() method, so we’ll add that to our code for Cart.cfc:

1
2
3
4
5
6
7
<cfcomponent>

  <cffunction name="isEmpty">
    <cfreturn true>
  </cffunction>

</cfcomponent>

This time when we run our spec output…

...we see green! We’ve met our first expectation.

An Equal and Opposite Expectation

The natural complement to our first expectation is that a cart should not always be empty, as our code implied. So let’s add an expectation that the cart should not be empty after adding an item to it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<cfimport taglib="/cfspec" prefix="">

<describe hint="Cart">

  <it should="be empty when first created">
    <cfset cart = createObject("component", "specdemo.Cart")>
    <cfset $(cart).shouldBeEmpty()>
  </it>

  <it should="not be empty after adding an item">
    <cfset cart = createObject("component", "specdemo.Cart")>
    <cfset item = stub()>
    <cfset cart.addItem(item)>
    <cfset $(cart).shouldNotBeEmpty()>
  </it>

</describe>

Line 14 is where our new expectation comes into play; it uses the same BeEmpty matcher, but it’s a negative expectation using shouldNot. We’ve also used a stub, or a placeholder object used for test purposes, since we don’t have a real Item object, nor do we care about it in this test. Refer to the wiki for more information about stubs in cfSpec.

Following along, with the output, we obviously need an addItem method on our cart. That’s easy:

1
2
3
4
5
6
7
8
9
10
11
<cfcomponent>

  <cffunction name="isEmpty">
    <cfreturn true>
  </cffunction>
  
  <cffunction name="addItem">
    <cfargument name="item">
  </cffunction>

</cfcomponent>

Voila! An addItem method. You’ll notice that I don’t bother writing any code unless I have an expectation failure complaining about it. This is a critical idea in the BDD cycle. If we jump ahead too much, we end up with code that has no test coverage—code that we expect to behave a certain way, but for which we have not actually delineated our expectations in a spec. With too much broken code coverage, the benefits of BDD are lost. Once you get used to it, you might even enjoy the way an expectation just carries you through the actual coding, and forces you to think more clearly about what you’re doing up front, when you write the spec.

Let’s see what the spec thinks of our new method:

Not too bad. It seems it’s time to actually figure out a way to get the cart to realize it’s no longer empty after an item is added. The simplest reasonable solution is usually the best. We could do this a number of different ways, but the most reasonable way, given our goal, is to have an instance variable which stores items that we add. I think that’s simple enough. Here’s my code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<cfcomponent>

  <cfset variables.items = []>

  <cffunction name="isEmpty">
    <cfreturn arrayIsEmpty(variables.items)>
  </cffunction>
  
  <cffunction name="addItem">
    <cfargument name="item">
    <cfset arrayAppend(variables.items, arguments.item)>
  </cffunction>

</cfcomponent>

...and, drum roll please…

Success!

How Many Items are in the Cart?

It’s great that we can find out whether or not the cart is empty, but on an e-commerce site that little cart icon usually tells how many items are in the cart. Let’s make that our next goal. The cart should know how many items it contains. Let’s write a spec that alternately adds items and checks the quantity making sure the numbers are correct:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<cfimport taglib="/cfspec" prefix="">

<describe hint="Cart">

  <it should="be empty when first created">
    <cfset cart = createObject("component", "specdemo.Cart")>
    <cfset $(cart).shouldBeEmpty()>
  </it>

  <it should="not be empty after adding an item">
    <cfset cart = createObject("component", "specdemo.Cart")>
    <cfset item = stub()>
    <cfset cart.addItem(item)>
    <cfset $(cart).shouldNotBeEmpty()>
  </it>

  <it should="know the number of items it contains">
    <cfset cart = createObject("component", "specdemo.Cart")>
    <cfset item1 = stub()>
    <cfset item2 = stub()>
    <cfset item3 = stub()>
    <cfset $(cart).shouldHave(0).items()>
    <cfset cart.addItem(item1)>
    <cfset $(cart).shouldHave(1).item()>
    <cfset cart.addItem(item2)>
    <cfset $(cart).shouldHave(2).items()>
    <cfset cart.addItem(item3)>
    <cfset $(cart).shouldHave(3).items()>
  </it>  

</describe>

That spec uses a new matcher, Have, which allows us to talk about how many elements are in a collection. It does this by looking for a named collection and/or asking for the collection’s size. Now that this spec is setup, we can let the output micro-manage our coding effort.

Presumably, the spec tried to find a named collection. Since we asked for items() it would have first tried getItems(), then, not finding it, it resorted to treating this as a generic collection and called size(). That’s fine for our purposes. Let’s add a size method.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<cfcomponent>

  <cfset variables.items = []>

  <cffunction name="isEmpty">
    <cfreturn arrayIsEmpty(variables.items)>
  </cffunction>
  
  <cffunction name="addItem">
    <cfargument name="item">
    <cfset arrayAppend(variables.items, arguments.item)>
  </cffunction>

  <cffunction name="size">
    <cfreturn arrayLen(variables.items)>
  </cffunction>

</cfcomponent>

Since it was clear enough and we’re getting the hang of this, I took the liberty of implementing this method all in the same step, rather than letting the spec complain about getting the wrong numbers. Nonetheless, we’ve got to make sure that I did the right thing…

Are you surprised?

Keep It Neat, Leave It Better

That’s all we’re going to implement of the cart for Part 1, but there is one final thing that we should do before we go. If you look at our last rendition of the spec, you’ll notice that each expectation starts with the creation of a cart object then we end up wrapping it with the $() function to call expectations on it. Let’s refactor that setup into a <before> block.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<cfimport taglib="/cfspec" prefix="">

<describe hint="Cart">

  <before>
    <cfset cart = createObject("component", "specdemo.Cart")>
  </before>

  <it should="be empty when first created">
    <cfset $(cart).shouldBeEmpty()>
  </it>

  <it should="not be empty after adding an item">
    <cfset item = stub()>
    <cfset cart.addItem(item)>
    <cfset $(cart).shouldNotBeEmpty()>
  </it>

  <it should="know the number of items it contains">
    <cfset item1 = stub()>
    <cfset item2 = stub()>
    <cfset item3 = stub()>
    <cfset $(cart).shouldHave(0).items()>
    <cfset cart.addItem(item1)>
    <cfset $(cart).shouldHave(1).item()>
    <cfset cart.addItem(item2)>
    <cfset $(cart).shouldHave(2).items()>
    <cfset cart.addItem(item3)>
    <cfset $(cart).shouldHave(3).items()>
  </it>  

</describe>

Notice in line 6 that I created the Cart object and stored it in the variable cart. This setup step is executed once for each <it> block, so that each expectation stays in isolation.

Conclusions

Using BDD can be a very rewarding development process. It can enhance the clarity of your code, improve readability, and give an automatic summary of what specifically you expect your code to do.

I hope that this tutorial was helpful and welcome any questions or feedback. In Part 2 of this tutorial, we will continue developing our Cart so that we can remove items, change item quantities, and get all the details that we need for displaying the cart to the user. Stay tuned.

10 Responses to “Getting Started with cfSpec (Part 1)”

  • nice example, Ron! Looking forward to seeing more.

  • Great work!

    Worked through the example and it feels good. Will there be an Eclipse plugin or running the tests? The mxunit one has been great!

  • Looks neat! Need to read your other posts but I’m curious how this works with TDD? Do you do this before writing your unit tests?

  • Thanks.

    @Alan, this project is still very young, but I’m aiming at full integration with IDEs (where possible), and with existing MVC frameworks.

    @Jim, BDD is actually a refinement of TDD, so this is really an improved version of unit testing.

  • Mitch Rose
    Mitch Rose

    Ron, I’m impressed with this work but also a bit confused with the choice of syntax. I’m looking at http://dannorth.net/whats-in-a-story and he’s using: Given [context] And [some more context]... When [event] Then [outcome] And [another outcome]...

    Can you help me ‘connect the dots’? thanks

  • Mitch, that’s an interesting observation. cfSpec will be able to work at two different levels: specs & stories. The specs are what we have been talking about so far, and are at the bottom level (similar to unit tests). Loosely speaking, the describe tags are the given, then there’s some setup which corresponds to the when, and finally expectations that represent the then outcome, as in “Given a new cart, when an item is added, then the cart should not be empty.”

    So why not use those exact words? I think that at this level of detail, it becomes less meaningful to use a Given/When/Then scheme. From the frameworks that I’ve used in other languages, I have found the particular syntax presented here more meaningful.

    However, BDD is a top-down approach, so sometime before cfSpec reaches version 1.0, the framework will also include a full story runner which uses a Given/When/Then scheme. This will allow us to write stories about the system from a higher level, that are meaningful to the client. This will facilitate an agile design process that let’s the client participate and possibly even write the rough drafts of stories.

  • Ryan McIlmoyl
    Ryan McIlmoyl

    Ron, Cool project. Similar to Alan’s question, are there any plans down the road to implement an ant task for cfspec?

    Also, I’ve noticed that mocks are coming up. Are you planning on writing this from the ground up, or implementing one of the existing CF mock frameworks (I’ve been using CFEasyMock lately and have really enjoyed it).

    Keep up the good work, I’ll be following this project.

  • Ryan, I’m definitely planning to provide an ant task… the more integration, the better.

    As far as mocking, I had something a little different in mind than CFEasyMock, however, I will most likely make an effort to accommodate integration with CFEasyMock for anyone who is already using it, or simply prefers that style.

  • I have been playing with (and enjoying!) cfspec and working with it on a bean extending a base bean that has the onMissingMethod() method.

    When I use

    I want it to call my size() method but it looks like oMM() comes into play and it calls getSomething() and oMM() is dealing with it instead and causing the behaviour to fail.

    Presuming I have got this right would it possible to do something in the spec that tells the spec to use size() as default rather than a getter method. Its no huge deal because I just implement the getter method and call size() from it but I am doing it to please the test and hooking test code into a production class has a slight ‘smell’ about it :-)

  • Alan,

    I see what you mean… I’ve noticed this sort of problem in another context, and I think I will be updating the code to first look for an actual method called getSomething. This will avoid automatic handling via onMM() in all such cases.

    I missed this for the recent v0.1.1 release, but it will definitely be in the next one. For the time-being, I would recommend using $(yourCollection).size().shouldEqual(n) rather than artifically putting getSomethings() into your production code.

Sorry, comments are closed for this article.