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:
public class VehicleThatHasBeenStartedBehaviors
protected static IVehicle vehicle;
It should_have_a_running_engine = () =>
It should_be_idling = () =>
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 = () =>
protected static Car vehicle;
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:
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 = () =>
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 = () =>
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.
For consistency alone, I think I would’ve preferred to see behaviours implemented like this:
It should_behave_like_a_started_vehicle = () =>
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).
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.
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.