Wednesday, June 20, 2012

Behavioral testing with NUnit

The traditional unit test works great for testing code functionality but another important testing aspect is testing code’s behavior and you don’t need to use a behavior testing framework like mspec to do it, with the right pattern nunit, mstest, and xunit work just fine.
A traditional TestFixture might look something like this
   1: [TestFixture]
   2: public class EmployeeRequests_Tests
   3: {
   4:     private EmployeeRequests target;
   5:     private Mock<IEmployeeRepository> MockEmployeeRepository;
   6:     [SetUp]
   7:     public void Setup()
   8:     {
   9:         target = new EmployeeRequests();
  10:         MockEmployeeRepository = new Mock<IEmployeeRepository>();
  11:         MockEmployeeRepository.Setup(x => 
  12:             x.GetEmployeeByCityState(It.IsAny<string>(), It.IsAny<string>()))
  13:             .Returns(new List<EmployeeDvr>
  14:                {
  15:                    new EmployeeDvr {City = "Seattle", State = "Washington", EmployeeName = "bob"}
  16:                });
  17:         target.EmployeeRepository = MockEmployeeRepository.Object;
  18:     }
  19:  
  20:     [Test]
  21:     public void ReturnsErrorForEmptyCity_Test()
  22:     {
  23:         target.GetEmployeeByLocation(string.Empty, "Washington");
  24:         var actual = target.Errors.Where(x => x.Contains("Invalid City"));
  25:         Assert.AreEqual(1,actual.Count());
  26:     }
  27:  
  28:     [Test]
  29:     public void ReturnsErrorForEmptyState_Test()
  30:     {
  31:         target.GetEmployeeByLocation("Seattle", string.Empty);
  32:         var actual = target.Errors.Where(x => x.Contains("Invalid State"));
  33:         Assert.AreEqual(1, actual.Count());
  34:     }
  35:  
  36:     [Test]
  37:     public void VailidCityStateCallRepository_Test()
  38:     {
  39:         target.GetEmployeeByLocation("Seattle", "Washington");
  40:         MockEmployeeRepository.Verify(x => x.GetEmployeeByCityState("Seattle", "Washington"));
  41:     }     
  42: }
Having a fairly large TestFixture with a lot of tests for the different functions of the method being called. 

When doing behavior testing the idea is to test a code path for a specific context vs. testing for all possibilities a method could result in, basically breaking it down into much easier to read parts.

To do this we set up a base or abstract class that has a context method for setting up your test and a because method for running it, here is an example of one using generics
   1: [TestFixture]
   2: public abstract class Test_Context<T> where T : new() 
   3: {
   4:     public T Target;
   5:     
   6:  
   7:     [SetUp]
   8:     public void Setup()
   9:     {
  10:         Context();
  11:         Because();
  12:     }
  13:  
  14:     public virtual void Context()
  15:     {
  16:         Target = new T();
  17:     }
  18:  
  19:     public abstract void Because();
  20: }
next we set up the the test fixture
   1: [TestFixture]
   2: public class When_Requesting_Employees_From_Seattle_Washington_Tests : Test_Context<EmployeeRequests>
   3: {
   4:     public Mock<IEmployeeRepository> MockEmployeeRepository;
   5:  
   6:     public override void Context()
   7:     {
   8:         base.Context();
   9:         MockEmployeeRepository = new Mock<IEmployeeRepository>();
  10:         MockEmployeeRepository.Setup(x => x.GetEmployeeByCityState("Seattle", "Washington"))
  11:             .Returns(new List<EmployeeDvr> { new EmployeeDvr { City = "Seattle", State = "Washington" } });
  12:         Target.EmployeeRepository = MockEmployeeRepository.Object;
  13:     }
  14:  
  15:     public override void Because()
  16:     {
  17:         Target.GetEmployeeByLocation("Seattle", "Washington");
  18:     }
  19:  
  20:     [Test]
  21:     public void HasNoErrors_Test()
  22:     {
  23:         Assert.AreEqual(0,Target.Errors.Count);
  24:     }   
  25:  
  26:     [Test]
  27:     public void Calls_The_Repository()
  28:     {
  29:         MockEmployeeRepository.Verify(x=>x.GetEmployeeByCityState("Seattle","Washington"),Times.Once());
  30:     }
  31: }
a couple of things you may notice is first the the name is fairly long and descriptive, the idea is you want to make it easy to read the test results to know what your testing, in this case requesting employees from Seattle, WA. 

The next thing you may notice is we don’t have as many test methods, sense we are only testing for what happens when we are getting employees from Seattle, WA. that’s all we are testing for.  This may produce more test textures classes but at the same the test fixtures are more targeted and give a better description of what you are testing making it easier to read/maintain later and is sometimes called executable specifications, where you can find out the intended function of a class or method simply by reading then names for the test fixture and tests.  For more examples of unit tests vs. behavoir tests  see the sample application in the sample code repository.

1 comment:

Bob The Janitor said...
This comment has been removed by the author.