After a trip to London to visit one of my friends who works for Rightmove -  the UK's No 1 Online Housing Site and we (we meaning I) got into talking about the ways we test our code. He told me that they started to use a Test Driven Development methodology. When testing in a development environment, their test framework used something called "mock objects" - these are objects that simulate behaviour, but in reality, don't do anything! I was intrigued....

Background

One of the drawback of the development process, especially with Test Driven Development, is that there comes a stage when a large amount of code has to be written to cater for a data access layer. Your program goes from:

public class DataAccessUser
{ 
     public User Load(string username)
     {
          return new User(username);
          //TODO: Write proper load
     }
}

public class BusinessObjectUser
{
     private string _username;

     public User(string username) 
     { 
          _username = username;
     }
}

To this:

public class DataAccessUser
{
     public User Load(string username)
     {          
          if( username == null )        
              throw new ArgumentNullException("username");          
          else               
              throw new ArgumentException("username","Username cannot be empty.");
          
          string connString = ConfigurationManager.ConnectionSettings["UserDB"].ConnectionString;
          SqlConnection sqlConn = new SqlConnection(connString);

          using(SqlCommand comm = sqlConn.CreateCommand())
          {
               User u = null;
               comm.Parameters.Add("@Username", username);

               sqlConn.Open();
               
               SqlDataReader dr = comm.ExecuteReader();
               
               if(dr.HasRows)
               {
                    User u = new User(dr.GetString( dr.GetOrdinal("Username"));
               }
               
               return u;
          }
     }
}

Now, I understand this is necessary as otherwise you can't load users. But now you have to test 2 parts of the code:

  1. The constructor for the User class.
  2. The DataAccessUser.Load() method.

In effect, you've written over 20 lines of code to test 2 cases line of code - the constructor call and a method call. You've also tied yourself into a specific technology for the data access layer, when you are only supposed to be concerned with testing!

Oops... you've got ahead of yourself. Now have to write another 5 test cases just for the Data Access layer.

  1. A successful BusinessObjectsUser load.
  2. An unsuccessful BusinessObjectsUser load.
  3. A null string parameter.
  4. An empty string parameter.
  5. An exception being generated for SQL being inaccessible.

This can be quite annoying as now you're losing track of your simple little BusinessObjectsUser class and loading it. In the process, have created nearly 3 times the code just by adding one method and not testing it.

Overview

Mock objects are simply an object that simulates the behaviour of a real object. A mock object is told how to behave when passed specific parameters. The idea behind this is that you are not bogged down with the technical details of the data access layer. Instead, you concentrate on the here and now, working on what is supposed to happen, rather than what will happen. Lets have a look at this in more detail.

Firstly, mock objects simulate fully implemented objects via the use of an interface. This means that in order to take advantage of mock objects, we need to use a base interface, which our real data access layer will implement. Using the example above, this would be:

internal interface IDataAccess
{
     User Load(string username);
}

Now, we need a way of telling the application to use an IDataAccess implementation we have created, instead of using a data access layer that provides real functionality.

Why? Well we want to have control over our data access layer in development, but in stage or production environments it will use the real thing. We do this using a pattern called the Dependency Injection pattern.

Dependency Injection Pattern

This pattern uses the idea that an interface is passed into an object and then the object calls the methods on that interface. As with interfaces, it doesn't have to know how the interface works, it just calls it and get its return values. This is exactly the behaviour we want! Test Driven Development works on the basis that you know how it is going to work, so you write your test case for its expected behaviour first, check it works (which it shouldn't) and then implement it. So I'll now implement a BusinessLogic class with the Dependency Injection pattern.

NB. Note from this point on we will be looking at testing this BusinessLogic class, because this is the class that uses the Dependency Injection Pattern.

public class BusinessLogic
{
     private IDataAccess _dataAccess;

     public BusinessLogic()
     {
          //Default assignment of IDataAccess goes here
     }

     public BusinessLogic(IDataAccess dataAccess)
     {
          _dataAccess = dataAccess;
     }

     public User LoadUser( string userName )
     {
          if(userName == null)
               throw new ArgumentNullException("userName");
          else if(userName.Length == 0)
               throw new ArgumentException("userName","Parameter cannot be empty.");
          else
               return _dataAccess.Load( userName );
     }
}

So we have 3 classes now:

  1. BusinessLogic, which calls
  2. IDataAccess, which uses
  3. BusinessObjects

This creates a performant and scalable architecture, but at the cost of flexibility.

Creating a mock object

In our test class, we can now create a mock object, which will emulate a class that inherits from IDataAccess.

[TestMethod()]
public void TestUserLoad()
{
     Mockery mock = new Mockery();
     IDataAccess da = mock.NewMock<IDataAccess>();
}

We now have a working IDataAccess implementation, but it doesn't do anything at all. In the NMock framework there is a class called the Stub class, which helps us tell the interface how to react for the created instance in this method.

In this example, we'll simulate the ArgumentException, when the .Load() method is called with an empty parameter. Remember we are now testing the BusinessLogic class, not the User, or IDataAccess layer:

[TestMethod(), ExpectedException(typeof(ArgumentException)]
public void TestUserLoadEmptyParameter()
{
     Mockery mock = new Mockery();
     IDataAccess da = mock.NewMock<IDataAccess>();
     Stub.On(da).Method("Load").With(string.Empty).Will(Throw.Exception( new ArgumentException() ));

     BusinessLogic bl  = new BusinessLogic(da);
     bl.LoadUser(string.Empty);

     Assert.Fail("ArgumentException has not been thrown!");
}

So how is this working? The BusinessLogic is using our "mock" IDataAccess interface, that we passed into the constructor. It is told to throw an exception when string.Empty is passed into the "Load" method. Simple!

I'll write a second test to check for a null parameter, a 3rd test to check for a valid user, and a 4th test for an invalid user. Remember, we are simulating the behaviour!

[TestMethod(), ExpectedException(typeof(ArgumentNullException)]
public void TestUserNullParameter
{
    Mockery mock = new Mockery();
     
    IDataAccess da = mock.NewMock<IDataAccess>(); 
    Stub.On(da).Method("Load").With( new object[] { null }).Will(Throw.Exception(new ArgumentNullException()));

    BusinessLogic bl = new BusinessLogic(da);
    User u = bl.LoadUser(null);

    Assert.Fail("ArgumentNullException should have been thrown!");
}

[TestMethod()]
public void TestValidUser
{
    Mockery mock = new Mockery();
     
    IDataAccess da = mock.NewMock<IDataAccess>(); 
    string username = "Billy";
    User u = new User(username);;

    Stub.On(da).Method("Load").With( username ).Will(Return.Value(u));

    BusinessLogic bl = new BusinessLogic(da);
    User u = bl.LoadUser(username);

    Assert.IsNotNull(u);
    Assert.AreSame(u.Username, username,"Usernames should be the same.");
}

[TestMethod()]
public void TestInvalidUser
{
    Mockery mock = new Mockery();
     
    IDataAccess da = mock.NewMock<IDataAccess>(); 
    string username = "Billy";

    Stub.On(da).Method("Load").With( username ).Will(Return.Value(null));

    BusinessLogic bl = new BusinessLogic(da);
    User u = bl.LoadUser(username);

    Assert.IsNull(u);
}

If you decided that you did have an IDataAccess implementing class, then you could create a method to automatically get this instance an plug it in as needed, instead of creating the mock object each time.

Real Implementation

Now, all you have to do is implement you IDataAccess interface with a real class, which would carry out the proper database access. The NMock framework and Dependency Injection pattern you have implemented gives you an excellent starting point for the expected behaviour of each method to IDataAccess.  This would have to be tested with unit tests too.

Confused?

At this point, you might be asking "What is the point? You aren't testing any data access!" and you are absolutely correct. This was the point that I stumbled over. The thing is, you would normally spend time writing your data access layer, but you would have to write tests to test this separately anyway! Unit testing is always about testing that a specific method is working in isolation. Even though our BusinessLogic.LoadUser() calls IDataAccess.Load(), we don't really care. What matters for this specific test,  is if the BusinessLogic.LoadUser() method is working and that it's working correctly in each of the 3 cases.

Summary

The NMock framework gives developers and testers an excellent starting point to development any object through the use of interfaces and easily customized behaviour within 3 lines of code.

The only cavaet of this, is that you MUST start using this as soon as you start development. Moving your project to an interface based approach can, although improve the testability of your code through the NMock framework, can easily get developers totally side-tracked. I wouldn't recommend moving to this if you are already in full flow.

I can assure you that from my experiences with it, that is an excellent example of self documenting code: short, yet descriptive and sets the building blocks in place for even the most complex of test cases.

Extra-credit

Another little tip is that some test frameworks are implemented as a separate project or assembly. The internal keyword does not allow any methods to be exposed outside of the assembly. Therefore, you need to add an attribute in your AssemblyInfo.cs to allow another namespace to access the internal methods:

[assembly: InternalsVisibleTo("AssemblyName")]

You are then able to access the internal constructors and methods of the referenced assembly.


Bookmark with :
Digg It! DZone StumbleUpon Technorati Reddit Del.icio.us Newsvine Furl Blinklist
posted @ Monday, July 21, 2008 1:14 PM |

Comments

No comments posted yet.

Post Comment

Title *
Name *
Email
Url
Comment *  


Please add 5 and 7 and type the answer here: