.Net Behavior Driven Development Using NUnit—Next Steps


Oren Eini left a comment on a recent post I did on Behavior Driven Development.  At first the questions seemed very easy and I thought I would be able to answer it within 15 minutes but as pondered the different possibilities of answering the question, I wanted to make sure that I gave an answer that was true to BDD as it relates to C#.  A day later, I finally came up with one but in the process I learned a great deal about the value of “context” and “behavior”, so I wanted to take this time to share what I have learned.

The questions Oren asked:

How would you handle a case such as:
The entity is valid IFF:
– The name is not empty
– The Date is greater than last year
– At least one of the following contact methods must be filled (phone, mobile, email)

Seems pretty easy right?  Well let’s take from a TDD perspective first.

Test Driven Development

This is a pretty basic test but let’s examine its structure more than what it is asserting.

    [TestFixture]    public class PersonValidatorTest    {        [Test]        public void ValidateThePersonWhenEmpty()        {            Person person = new Person();             person.Name = null;            person.WhatEverDate = DateTime.Today.Subtract(new TimeSpan(362));            person.Phone = null;            person.Mobile = null;            person.Email = null;             Assert.IsFalse(PersonValidator.IsValid(person));        }

What can we gather from this test?

·         We know from the test fixture name that this class contains test about the PersonValidator class

·         We know that the test has to do with validating the person object when it is empty

·         We know that the test sets up a person object and sets all the accessors to null and sets the date property to 1 year from today

·         We know that the test call the IsValid static method on the PersonValidator objects and asserts that the result is false

Hmm… There is a lot going on here in this simple test.  But can anyone tell me “Why?” you are doing this test?  Why are you asserting that the PersonValidator specification validates the Person object?  You may know implicitly why you are creating this test but a year later would you know why?  Ok maybe I am getting a little too deep here but the premise is we have lost something during TDD.

So how do we clarify the test to know the “Why”

I am going to have to expand on Oren’s question a little to gain greater incite.

Taking a cue from Dan North again, we use the following template:

 

Scenario 1: Person information is filled in

Given the user has filled out a person’s information

When the user is about to save the persons information

Then make sure the name is not empty

and the date is greater than the last year

and at least one of the following contact fields must be filled in (Phone, Mobile, Email)

 

Let’s see how we would write a specification that captures the behavior of PersonValidator.  Consider the following:

namespace Domain.PersonValidatorSpecifcation{    [TestFixture]    public class APersonWithInformationFilledIn    {        [Test]        public void MakeSureTheNameIsNotEmpty()        {                    }    }}

This BDD example gives us quite a bit of information.

·         According to the namespace all of the “contexts” in this file deal with the PersonValidator object

·         We look at the test fixture to determine the context, in this case it is “A person with information filled in”

·         The first specification “Make sure the name is not empty”

So let’s read it as if it would appear in the test runner:

Domain.PersonValidatorSpecificaton.APersonWithInformationFilledIn.MakeSureTheNameIsNotEmpty

I don’t know about you but that doesn’t read well to me, it needs to more explicit about why we need this specification.  Remember we are declaring the Person object invalid if it is in this context. Does the spec name explain that concept? How about this?

Domain.PersonValidatorSpecificaton.APersonWithInformationFilledIn.ShouldBeInValidIfTheNameIsBlank

That’s much better.

So now the code reads like this:

namespace Domain.PersonValidatorSpecifcation{    [TestFixture]    public class APersonWithInformationFilledIn    {        [Test]        public void ShouldBeInValidIfTheNameIsBlank()        {                    }    }}

So like traditional TDD you go through the first two phases “Red”, “Green” But let’s talk about the “Refactoring” exercise.  (I am not going to go into the actual validator code let’s just assume it does what it says.)

namespace Domain.PersonValidatorSpecifcation{    [TestFixture]    public class APersonWithInformationFilledIn    {        [Test]        public void ShouldBeInValidIfTheNameIsBlank()        {            Person person = new Person();                        person.Name = null;            person.WhatEverDate = DateTime.Today.Subtract(TimeSpan.FromDays(367));            person.Phone = null;            person.Mobile = null;            person.Email = null;             Assert.IsFalse(PersonValidator.IsValid(person));        }    }}

This test works as is but this isn’t the only specification we are going to write and I don’t want to create the Person object every time. So let’s refactor. Remember unlike traditional TDD we are asserting within a context in BDD.  The context of this fixture is “A person with information filled in” so we need to “SetUp” the context.

namespace Domain.PersonValidatorSpecifcation{    [TestFixture]    public class APersonWithInformationFilledIn    {        private Person person;          [SetUp]        public void SetUp()        {            person = new Person();            person.Name = null;            person.WhatEverDate = DateTime.Today.Subtract(TimeSpan.FromDays(367));            person.Phone = null;            person.Mobile = null;            person.Email = null;         }         [Test]        public void ShouldBeInValidIfTheNameIsBlank()        {            Assert.IsFalse(PersonValidator.IsValid(person));        }    }}

Well that’s a lot better but wait a minute.  The context reads “A person with information filled in” according to our setup there isn’t anything filled in.  We must stay true to the context. So let’s continue to refactor.

namespace Domain.PersonValidatorSpecifcation{    [TestFixture]    public class APersonWithInformationFilledIn    {        private Person person;          [SetUp]        public void SetUp()        {            person = new Person();                        person.Name = “Joe Smith”;            person.WhatEverDate = DateTime.Today.Subtract(TimeSpan.FromDays(367));            person.Phone = “555-5555”;            person.Mobile = “555-5555”;            person.Email = “555-5555”;         }         [Test]        public void ShouldBeInValidIfTheNameIsBlank()        {            Assert.IsFalse(PersonValidator.IsValid(person);        }    }}

Much better but wait, you run the unit test and it fails. You need to clear the Name property since this is what you are specifying on.

namespace Domain.PersonValidatorSpecifcation{    [TestFixture]    public class APersonWithInformationFilledIn    {        private Person person;          [SetUp]        public void SetUp()        {            person = new Person();                        person.Name = “Joe Smith”;            person.WhatEverDate = DateTime.Today.Subtract(TimeSpan.FromDays(367));            person.Phone = “555-5555”;            person.Mobile = “555-5555”;            person.Email = “555-5555”;         }         [Test]        public void ShouldBeInValidIfTheNameIsBlank()        {            person.Name = null;            Assert.IsFalse(PersonValidator.IsValid(person));        }}}

Now everything passes again but let’s talk about passing. We know that the person object “ShouldBeInValidIfTheNameIsBlank” so can we infer that if it isn’t blank that it should pass.  I believe we can as long as the domain specifications allow it and in this case they do.  So you can assert on both aspects of the specification since you have isolated the context around the spec.

namespace Domain.PersonValidatorSpecifcation{    [TestFixture]    public class APersonWithInformationFilledIn    {        private Person person;          [SetUp]        public void SetUp()        {            person = new Person();                        person.Name = “Joe Smith”;            person.WhatEverDate = DateTime.Today.Subtract(TimeSpan.FromDays(367));            person.Phone = “555-5555”;            person.Mobile = “555-5555”;            person.Email = “555-5555”;         }         [Test]        public void ShouldBeInValidIfTheNameIsBlank()        {            person.Name = null;            Assert.IsFalse(PersonValidator.IsValid(person));            person.Name = “John Glenn”;            Assert.IsTrue(PersonValidator.IsValid(person));        }}}

At this point you I think we can finish the rest of the specifications.

namespace Domain.PersonValidatorSpecifcation{    [TestFixture]    public class APersonWithInformationFilledIn    {        private Person person;          [SetUp]        public void SetUp()        {            person = new Person();                        person.Name = “Joe Smith”;            person.WhatEverDate = DateTime.Today.Subtract(TimeSpan.FromDays(367));            person.Phone = “555-5555”;            person.Mobile = “555-5555”;            person.Email = “billy@bob.com”;         }         [Test]        public void ShouldBeInValidIfTheNameIsBlank()        {            person.Name = null;            Assert.IsFalse(PersonValidator.IsValid(person));            person.Name = “John Glenn”;            Assert.IsTrue(PersonValidator.IsValid(person));        }         [Test]        public void ShouldBeInValidIfTheWhatEverDateIsGreaterThanLastYear()        {            person.WhatEverDate = DateTime.Today;            Assert.IsFalse(PersonValidator.IsValid(person));            person.WhatEverDate = DateTime.Today.Subtract(TimeSpan.FromDays(367));            Assert.IsTrue(PersonValidator.IsValid(person));        }         [Test]        public void ShouldBeInValidIfAtLeastOneOfTheFollowingFieldsIsBlankPhoneMobileEmail()        {            person.Phone = null;            Assert.IsFalse(PersonValidator.IsValid(person));            person.Phone = “555-5555”;            Assert.IsTrue(PersonValidator.IsValid(person));             person.Mobile = null;            Assert.IsFalse(PersonValidator.IsValid(person));            person.Mobile = “555-5555”;            Assert.IsTrue(PersonValidator.IsValid(person));             person.Email = null;            Assert.IsFalse(PersonValidator.IsValid(person));            person.Email = “Test@test.com”;            Assert.IsTrue(PersonValidator.IsValid(person));         }    }}It is important to note that BDD allows the context and specifications of the test to be easily shared.  The next steps would be to use the XML that NUnit produces and create a style sheet that will display the following information similar to AgileDox.Person Validator Specifcation    A person with information filled in should be inValid if the name is blank : passed should be inValid if the what ever date is greater than last year : passed should be inValid if at least one of the following fields is blank phone mobile email : passed

Summary

I know I have covered a lot of concepts in this post but my hope, is that I sparked interest into the realm of Behavior Driven Development.  I want to mention that by no means am I saying that this is the defacto way to practice BDD is .Net but merely an idea or concept that helps to gain the acceptance and value of BDD within the .Net community.

Guidelines to BDD in .Net

·               Utilize the Namespace to categorize the domain entities

·               The class names should convey the context of the specification you are asserting.

·               Keep the SetUp of the test fixture true to the context.

·               The method name should convey the specification you are asserting.

·               Stay true to the principles of TDD Red, Green, Refactor!

Our Teams Development Lab