BDD With MSTest


Update: this blog is no longer active. For new posts and RSS subscriptions, please go to http://saintgimp.org.

In my previous post I mentioned that I was writing BDD-style unit tests in the standard MSTest environment that’s part of Visual Studio.  In an ideal world, I’d probably choose to use a test framework explicitly designed for BDD like MSpec, but there’s a maximum rate of change that I and my team can absorb and right now a new test framework would exceed that boundary.

Instead, I use a small adapter class to translate MSTest’s attribute system to my preferred BDD context/specification style.  The adapter class is very trivial, but I thought I’d post it here in case anyone wants to see it.

/// <summary>

/// Provides common services for BDD-style (context/specification) unit tests.  Serves as an adapter between the MSTest framework and our BDD-style tests.

/// </summary>

public abstract class ContextSpecification

{

    private TestContext testContextInstance;

    /// <summary>

    /// Gets or sets the test context which provides information about and functionality for the current test run.

    /// </summary>

    public TestContext TestContext

    {

        get { return this.testContextInstance; }

        set { this.testContextInstance = value; }

    }

    /// <summary>

    /// Steps that are run before each test.

    /// </summary>

    [TestInitialize()]

    public void TestInitialize()

    {

        this.Context();

        this.BecauseOf();

    }

    /// <summary>

    /// Steps that are run after each test.

    /// </summary>

    [TestCleanup()]

    public void TestCleanup()

    {

        this.Cleanup();

    }

    /// <summary>

    /// Sets up the environment for a specification context.

    /// </summary>

    protected virtual void Context()

    {

    }

    /// <summary>

    /// Acts on the context to create the observable condition.

    /// </summary>

    protected virtual void BecauseOf()

    {

    }

    /// <summary>

    /// Cleans up the context after the specification is verified.

    /// </summary>

    protected virtual void Cleanup()

    {

    }

}

That’s all there is to it.  Note that Context() and BecauseOf() are run before every test, even though technically they could be run only once for all the tests in a particular context class.  This is just to make absolutely certain that no side-effects bleed through from one test to another.

There are a couple of other conventions that we use to make the TDD/BDD process better.  The first is that we put common context code into a shared context class, then derive our test context classes from that.  We try to keep the shared context class very simple; mostly just declaring and instantiating any common fields or stubbed interfaces and declaring and instantiating the class under test.  If you limit yourself to just those steps in the shared context class then your tests will still be self-explanatory and understandable.  Beware, though, of cramming too much complex setup code into the shared context class because if you do, no one will be able to understand your tests without flipping back and forth between the test and the shared context class.

The second convention is that we place all the test context classes inside of an empty static class named for the class or concern we’re testing.  (Update: edited to better match the screenshot below.)  So, for example, all of our EncryptionService tests are contained inside of a static class named EncryptionServiceTests.

The only reason for this is to help disambiguate the tests in the Test Results window after you run all your tests.  With the combination of the BDD naming scheme and the MSTest runner, it can be hard to tell which component a failing test is targeting.  The solution is to use a containing class as described above and then add the “Class Name column to the Test Results window, like so:

  1. in the Test Results window, right click on a column header.
  2. Choose Add/Remove Columns.
  3. Enable the “Class Name” column and move it up to before “Test Name”.
  4. Click OK.

Now your test results will look like this:

image

Sadly, Visual Studio doesn’t save your column preferences so you have to re-enable the Class Name column every time you start Visual Studio.  Grrrr.

So here’s what it looks like all put together (I trimmed this code down to one test for space reasons):

public static class EncryptionServiceTests

{

    public class EncryptionServiceContext : ContextSpecification

    {

        protected string plainText;

        protected string encryptedText;

        protected EncryptionService encryptionService;

        protected override void Context()

        {

            this.encryptionService = new EncryptionService();

        }

    }

    [TestClass]

    public class when_empty_encrypted_text_is_submitted_for_decryption : EncryptionServiceContext

    {

        protected override void BecauseOf()

        {

            this.plainText = this.encryptionService.Decrypt(string.Empty);

        }

        [TestMethod]

        public void the_plaintext_should_be_empty()

        {

            this.plainText.ShouldBeEmpty();

        }

    }

}

Comments (8)

  1. Thank you for submitting this cool story – Trackback from DotNetShoutout

  2. Kim Stevens says:

    @Eric I’ve been playing around with a way to test exceptions using your approach and without using the ExpectedExceptionAttribute –  http://kimstevens.posterous.com/testing-exceptions-with-bdd-style-mstest

    Have you discovered a better way?

    Cheers,

    Kim

  3. SaintGimp says:

    I use pretty much the same technique as you described in your blog (see these posts of mine: http://blogs.msdn.com/elee/archive/2009/01/25/bdd-specification-extensions.aspx and http://blogs.msdn.com/elee/archive/2009/01/25/handling-exception-in-bdd-style-tests.aspx).

    It’s certainly more verbose than the attribute approach but it fits better with the separation in the BDD style and more importantly, it’s explicit about what should cause the exception.  With the attribute the test will pass if *anything* throws the expected exception, even something you weren’t trying to test.

  4. Kim Stevens says:

    Thanks Eric! I’ve modified my approach after reading those – much happier now 🙂

    http://kimstevens.posterous.com/testing-exceptions-with-bdd-style-mstest-take

  5. Rodd Harris says:

    Thanks Eric.  I’m new to this stuff (and to the .Net community) and this has been realy helpful.

    I do have one question.  It’s not a problem but more that I want to make sure I’m not missing something here.  In your ContextSpecification class, you define TestContext property and the testContextInstance field.  However, it doesn’t look like they’re ever used.  Is that correct?

    If so, why define them at all — or are they required to exists for MSTest to be happy?

  6. SaintGimp says:

    @Rodd: The TestContext property is optional but can provide some useful features for certain kinds of tests.  Basically, if your test class has this property then MSTest will set it for you before the tests run and you can use it for various purposes.  If you don’t need to use the TestContext class in your tests then you don’t need that property and field.  See this link: http://msdn.microsoft.com/en-us/library/ms404699(VS.90).aspx

  7. kyle bailey says:

    Great article! I definetly am picking up a few pointers as I’m trying to put all of this BDD stuff together.