Earlier, I wrote about how I think unit tests should employ the concept of falsifiability. To do this effectively, I’ve been experimenting a bit with how I structure my tests. Following on from the concepts I talked about previously, each test fixture should be defined as a hypothesis we try to falsify with a set of assertions – not being able to break the hypothesis means we can assume that it is true (and that our code works!).
This ties neatly with the concepts of BDD, which uses a context/specification style for writing tests, as well as the now popular AAA (Arrange-Act-Assert) structure. Much of the inspiration for what I’m doing here comes from reading Jean-Paul Boodhoo’s and Jimmy Bogards blogs.
Here’s an example of a test class I’ve written in this way:
namespace Specs_for_ImageController
{
public class When_rendering_image : RenderImageHypothesis
{
private DynamicImageResult _result;
private const string _id = "id";
protected override void Observe()
{
_result = _controller.Render(_id, null, null) as DynamicImageResult;
}
[Fact]
public void It_is_retrieved_from_gallery()
{
// verify that the gallery's GetItem method was called
_galleryMock.Verify(g => g.GetItem(_id));
}
[Fact]
public void A_DynamicImageResult_is_returned()
{
Assert.NotNull(_result);
}
[Fact]
public void DynamicImageResult_has_image()
{
Assert.Same(_image, _result.Image);
}
}
}
What I’ve done here, is separate the context (Arrange) of the hypothesis from the observation (Act) and the attempts to falsify it (Assert). Additionally, I’m using the namespace, class and method to build a very fluent naming scheme.
The test class inherits from the RenderImageHypothesis class, which contains the context of the hypothesis:
public abstract class RenderImageHypothesis : Hypothesis
{
protected ImageController _controller;
protected Mock<IGallery> _galleryMock;
protected Image _image;
protected Mock<IGalleryItem> _galleryItem;
protected Mock<IImageScaler> _imageScalerMock;
protected Bitmap _scaledImage;
protected override void InitializeContext()
{
_galleryItem = new Mock<IGalleryItem>();
_galleryMock = new Mock<IGallery>();
_imageScalerMock = new Mock<IImageScaler>();
_image = new Bitmap(1, 1);
// mock the gallery item so that it returns the image
_galleryItem.Expect(g => g.GetImage()).Returns(_image);
// mock the gallery so that it returns the gallery item
_galleryMock.Expect(g => g.GetItem(It.IsAny<string>())).Returns(_galleryItem.Object);
// mock the image scaler so that it returns the 'scaled' image
_scaledImage = new Bitmap(1,1);
_imageScalerMock
.Expect(s => s.Scale(It.IsAny<Image>(), It.IsAny<int?>(), It.IsAny<int?>()))
.Returns(_scaledImage);
_controller = new ImageController(_galleryMock.Object, _imageScalerMock.Object);
}
}
This then enables me to reuse it for other observations on the same context:
public class When_rendering_image_with_width_and_height_defined : RenderImageHypothesis
{
private readonly int? _width = 15;
private readonly int? _height = 15;
private DynamicImageResult _result;
protected override void Observe()
{
_result = _controller.Render("", _width, _height) as DynamicImageResult;
}
[Fact]
public void It_is_scaled_to_width_and_height()
{
// verify that the image scaler was called with the appropriate args
_imageScalerMock.Verify(s => s.Scale(_image, _width, _height));
}
[Fact]
public void Scaled_image_returned()
{
Assert.Same(_scaledImage, _result.Image);
}
}
My Hypothesis base class looks like this:
/// <summary>
/// Represents a test fixture hypothesis
/// </summary>
public abstract class Hypothesis
{
protected Hypothesis()
{
InitializeContext();
Observe();
}
/// <summary>
/// Should initialize the context of the test fixture
/// </summary>
protected abstract void InitializeContext();
/// <summary>
/// Should perform the observation that will be asserted
/// </summary>
protected abstract void Observe();
}
Running the tests, we get a very nice BDD-style list of test results:
I’m still experimenting with this, and I’ve yet to convince myself 100% that its a better way to structure the tests – especially with respect to separating out the context in a base class for reuse, which could potentially lead to hard to tests that are hard to understand at a glance. If you have any thoughts, please comment!