Hypothesis Base Class and Naming Conventions for BDD/AAA Style Unit Testing

An entry about tdd | bdd Publication date 22. August 2008 11:57

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:

image

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!

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Comments

Powered by BlogEngine.NET 1.4.5.0

Welcome!

My name is Fredrik Kalseth, and this is my blog - thanks for visiting! I am fortunate enough to work with what I love for a living, and this blog is essentially the biproduct of that.

I work as a senior consultant for Capgemini, and am also an active participant in the Norwegian .NET community, as an avid attendee but also as a speaker (most recently at NNUG and MSDN Live).

As a developer, I have a wide circle of interest. My primary passion is for agile, test-driven development, with focus on best practices and clean code. That said, I also love to work on the frontend, especially with web development.

On Twitter? My handle is fkalseth. On LinkedIn? I`m there too.

NDC 2010

The conference to attend this summer happens June 16th-18th in Oslo, Norway. Are you going? Be sure to catch my talk on AOP while you're there!

 

Disclaimer

This is a personal blog; any opinions expressed here are my own and do not necessarily reflect those of my employer. All content herein is my own original creation, and as such is protected by copyright law. Unless otherwise stated, all source code posted on this blog is freely usable under the Microsoft Permissive License.

What Readers Talk About

Comment RSS