BDD With MSTest

(I originally posted this on my MSDN blog.)

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.

///

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

///

public abstract class ContextSpecification

{

    private TestContext testContextInstance;

    ///

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

    ///

    public TestContext TestContext

    {

        get { return this.testContextInstance; }

        set { this.testContextInstance = value; }

    }

    ///

    /// Steps that are run before each test.

    ///

    [TestInitialize()]

    public void TestInitialize()

    {

        this.Context();

        this.BecauseOf();

    }

    ///

    /// Steps that are run after each test.

    ///

    [TestCleanup()]

    public void TestCleanup()

    {

        this.Cleanup();

    }

    ///

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

    ///

    protected virtual void Context()

    {

    }

    ///

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

    ///

    protected virtual void BecauseOf()

    {

    }

    ///

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

    ///

    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_15

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();

        }

    }

}

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.