Sunday, December 21, 2008

Test Data Builder

In every project you reach a point where you need test data for unit testing. Having a reasonably complex domain model can make this task "difficult" because testing an entity in many cases means that you also have to setup associations to other entities. Intializing objects per test is cumbersome and any changes to constructor arguments will break your tests. One of the solutions to this problem is to use the Object Mother pattern. It's basically a class with factory methods that helps you setup an object for testing. The object creation code is moved out of the tests so that it can be reused and making the test data more maintainable. So if you are developing a scrum application and want to create a project with a sprint and a user story you would do something like this:

public static class ProjectMother
{
 public static Project CreateProjectWithSprint()
 {
     Project project = new Project();
     Sprint sprint = new Sprint();
     UserStory userStory = new UserStory();
     userStory.Name = "User story";
     userStory.StoryPoints = 8;
     sprint.AddUserStory(userStory);
     project.AddSprint(sprint);
     return project;
 }
}

Project project = ProjectMother.CreateProjectWithSprintAndUserStories();
However as time goes by you end up with a lot of factory methods for the slightest variation in the test data beacuse of the heavy coupling that exists since many tests use the same method. This make the Object Mother class hard to maintain. To solve this problem I usually write a fluent interface (embedded domain specific language) that I use to initalize my objects for testing. This is heavily based on the Expression Builder pattern. For each class I want to test I write a builder for that class. So when I want to create a Project object I just write:
Project project = ProjectBuilder.Create.Project
              .WithName("Test Project")
              .WithSprint(SprintBuilder.Create.Sprint
                  .WithName("Sprint 1")
                  .WithUserStory(UserStoryBuilder.Create.UserStory
                      .WithName("Story 1")
                      .WithStoryPoints(13)
                      .WithTask(TaskBuilder.Create.Task
                          .WithName("Task 1")
                          .WithHours(3))));

Behind the scenes the ProjectBuilder takes care of everything adding sprints and so on. In each method the builder just returns itself after having done some setup on the private Project instance. The ProjectBuilder is finally casted to Project using an implicit cast operator which returns the private Project instance.
public class ProjectBuilder
{
 private static Project mProject;

 public static ProjectBuilder Create
 {
     get { return new ProjectBuilder(); }
 }

 public ProjectBuilder WithName(string name)
 {
     mProject.Name = name;
     return this;
 }

 public ProjectBuilder Project
 {
     get
     {
         mProject = new Project { Name = "Test Project" };
         return this;
     }
 }

 public ProjectBuilder WithSprint(Sprint sprint)
 {
     mProject.AddSprint(sprint);
     return this;
 }

 public ProjectBuilder WithBacklog(Backlog backlog)
 {
     mProject.Backlog = backlog;
     return this;
 }

 public static implicit operator Project(ProjectBuilder builder)
 {
     return mProject;
 }

}
Still, I don't feel that the Object Mother and the Builder are mutually exclusive. If I have a lot of tests that use the same test data I often create an Object Mother with a factory method that uses the builders. When I need a specialized initialization of an object for a test I just use the builders directly in my tests.

I find this way of creating test data really useful and hopefully you do too! Feel free to download and use the code. The builders are located in the tests project.

Merry Christmas!!

2 comments:

  1. Interesting post. A nice and simple example of how to create a fluent interface, and how it can help with testing.

    I've used the ObjectMother pattern for testing my self, and I agree that they can grow to big at times... So the fluent interface will be a nice addition to keep the test suite nice and maintainable.

    Merry Christmas!

    ReplyDelete
  2. There is a lot of competition in outsourcing software development, as there are many firms across the globe catering to clients looking for outsourcing their work. What is good is that the takers can choose the best from the lot. http://www.infysolutions.com.

    ReplyDelete