Behaviours in MSpec

An article on Behaviours in MSpec came up on my Google Reader a few days ago, and whilst I think the general concept looks really  good, I wanted to share some thoughts.

For those unfamiliar with MSpec, I would suggest reading this post first to get acquainted with the general ideas and syntax.

First of all, lets have a look at what “behaviours” in MSpec are:

Behaviours define reusable specs that encapsulate a particular set of, you guessed it, behaviours; you’re then able to include these specs in any context that exhibits a particular behaviour.

The example provided in the article was Cars and Motorbikes sharing common behaviour – that of a vehicle.  By specifying the behaviour of the vehicle base class (or expected behaviour of an implementation of an interface), subclasses (or specific implementations) can be easily tested against this behaviour, while keeping your tests nice and DRY.

So given the following behaviours for a vehicle:

[Behaviors]
public class VehicleThatHasBeenStartedBehaviors
{
  protected static IVehicle vehicle;
  
  It should_have_a_running_engine = () =>
    vehicle.IsEngineRunning.ShouldBeTrue();
 
  It should_be_idling = () =>
    vehicle.RevCount.ShouldBeBetween(0, 1000);
}

We can test that a car conforms to these behaviours, without duplicating this definition:

public class when_a_car_is_started
{
  Establish context = () =>
    vehicle = new Car();
  
  Because of = () =>
    vehicle.StartEngine();
  
  Behaves_like<VehicleThatHasBeenStartedBehaviors> a_started_vehicle;
  
  protected static Car vehicle;
}

Intuitive code

There are a couple of things that stood out as looking kinda funky about the implementation of behaviours (over and above the rest of the mspec syntax anyway).

The first thing that looked funky was this line:

Behaves_like<VehicleThatHasBeenStartedBehaviors> a_started_vehicle;

My initial reaction to this was, “hmm, it’s a field – that doesn’t seem like a very intuitive way of defining a test case”.  Given some more thought, I remembered that actually, the all assertions in mspec are defined through fields:

  It should_have_a_running_engine = () =>
    vehicle.IsEngineRunning.ShouldBeTrue();

So what’s my problem?  Personally, I don’t think its very clear that this line is a an assertion, and I think it’s because it’s not obvious how the behaviour syntax is achieving the execution of the behaviour. Whenever I’m writing tests and I want to say that a class behaves in a certain way, I’m going to have to stop and think hard (or google) to get the right syntax. That extra cognitive effort will get in the way of other, more important things I should be thinking about.

So why don’t I have a problem with the rest of the “funky” mspec syntax?!

Let me explain.

It’s not too much of a leap to see how when executing tests, the mspec test runner actually uses the value of the field (which is a delegate) to define the test. Consider that we could have written the mspec test like this:

  private It should_have_a_running_engine = () =>
  {
        Assert.IsTrue(vehicle.IsEngineRunning);
  }

So yeah, just a field. The fields value is a delegate defining the test, and will be invoked by the test runner. This is akin to how the “traditional” nUnit test runner would execute methods decorated with the [Test] attribute. Not too difficult.

The behaviour definition is slightly different however. Rather than defining the assertion inline as delegate, the behaviour field actually has no value. Instead, the behaviour is specified in the class provided as a generic parameter. It is not immediately obvious how a field with no value can be used to define an assertion.

An alternative?

For consistency alone, I think I would’ve preferred to see behaviours implemented like this:

It should_behave_like_a_started_vehicle = () =>
        vehicle.BehavesLike<VehicleThatHasBeenStartedBehaviors>();

This way, the test follow a common structure, and IMHO, is easier to grok. A downside of this approach however (although I haven’t explored whether the current implementation suffers the same way) is that when executing these specifications, the test runner will see this as a single item (rather than nested behaviours).

Other thoughts

An additional caveat mentioned in the post about the behaviour syntax was that “fields must be protected, both in the behaviour and the contexts; and the fields must have identical names”. I believe this convention is required because, as far as I can tell, the protected field is needed to provide an instance of the subject under test.

I think this restriction could cause no end of pain when trying to figure out why the feck a behaviour is being ignored. Perhaps if alternative “It should” mechanics I have suggested were used, these constraints wouldn’t need to exist, as an instance of the SUT would be provided in the delegate.

Final Thoughts

Even with these reservations, I do like how MSpec is making it easier for me to write DRY, readable tests. I will have to tread carefully when implementing behaviours however, as I’m not 100% convinced about the “discoverability” of this part of the API.

About these ads

About craigcav

Craig Cavalier works as a Software Developer for Liquid Frameworks in Houston Tx, developing field ticketing and job management solutions for industrial field service companies.

Posted on January 27, 2010, in Uncategorized. Bookmark the permalink. 3 Comments.

  1. Craig,

    Behaviors are implemented such that it is possible to generate specifications on the fly, allowing us to display the behavior’s specification text (“should do blah”) in various places like the ReSharper test runner or the HTMl reports. We do that exclusively through reflection, which requires us to place the behavior type somewhere in the assertion field as a generic parameter.

    The syntax you suggest would not allow for that. I agree that yours is easier to grok. Ours just reads like a statement, without detailing what’s going on. To the developer that’s not inclined in the code or how the MSpec does its magic that might suffice.

    Regarding the visibility and naming of fields in the context and the behavior: you are correct about those conventions. MSpec copies all protected field values over to the behavior before running the behavior’s specifications.

    As the original developer of behaviors I guess I’m responsible for the missing documentation of that feature.

    That said, and from my own experience with MSpec, behaviors are a tool intended for special cases. In my current project with > 1000 specs we use it in exactly one file.

    Alex

  2. Excellent idea!!!. r u going to sumbit it to mspec source code?

  3. Hi Craig,

    Behaviors are actually not documented or mentioned on my blog on purpose. I figured someone would find it eventually, but I didn’t want to promote it. I actually considered removing it.

    One of the biggest goals of mspec and context/spec in general is to make the tests easily scannable. Behaviors breaks this. It plays to our (being geeks) need to apply tools and methods we know without considering context.

    DRY does *not* apply to tests the same way it applies to code. DRYing up your tests is local optimization at its finest. By doing it, you reduce the lines of code but you also reduce the ease of understanding and ability to scan the test files. Don’t get me wrong, some cruft still should be abstracted/hidden, but the *core* of your specs is the specifications themselves. Behaviors hide those behind another class making full understanding from the code an extremely jarring experience.

    Even if your proposed syntax was possible and adopted, it would only help one of the cognition issues–a cognition issue that is really only transient and could be (more easily) relieved by just seeing “Behaves_like” a few times to know what it implies.

    Aaron

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: