Applying Fluent Interface. Part I - Test Data Builders.
Fluent interface is not as famous of a pattern as the Strategy Pattern but average developer encounters it far more often, sometimes not even realising it. Take C# String object for example: MyString.ToUpper().Trim().Replace("a", "A") – that’s a pretty much daily code.
In a sense Fluent Interface has an application to the state of the object similar to one which Decorator Pattern has to the structure of the class. Some kind of State Decorator Pattern – although the idea of the Fluent Interface is to produce deterministic results, while Decorator potentially could be designed in opposite way (though I can’t think of the reason as yet).
XP Toronto user group hosted “Maintainable Unit Tests” presentation where fluent interfaces were helping to create expandable test data builders (it is a little bit more organized in the Colin Jack’s post).
Imagine, you have Student object
public class Student
{
public string Name { get; set; }
public DateTime DateOfBirth { get; set; }
public double Average { get; set; }
public Discipline Major { get; set; }
}
and you’re about to build unit tests which will evaluate business rules around any single property and even different combinations of them. Generating test objects may be tedious job which is often assigned to Test Set Up methods. As you progress, you may notice that some of the helper methods are useful between test modules and then you decide to extract this functionality to helper classes, which are evil.
Instead it is better to go the right OOP way and create classes which expose generic functionality and provide us with concrete instances of out domain entities to test.
Essentially it is an implementation of Builder and Factory (or Object Mother) patterns. Every domain entity’s property has a corresponding method in the relevant Data Builder class. Some of the properties, which we are not too much concerned about (like Name in the example) can be simply set to some default values.
public interface IDataBuilder<T>
{
T Build();
}
public class StudentDataBuilder : IDataBuilder<Student>
{
private DateTime dateOfBirth { get; set; }
private double average { get; set; }
private string major { get; set; }
public Student Build()
{
return new Student
{
Name = "John Smith",
DateOfBirth = dateOfBirth,
Average = average,
Major = major
};
}
public static StudentDataBuilder New()
{
return new StudentDataBuilder();
}
public StudentDataBuilder WithAverage(double value)
{
return new StudentDataBuilder
{
dateOfBirth = dateOfBirth,
average = value,
major = major
};
}
public StudentDataBuilder Taking(string value)
{
return new StudentDataBuilder
{
dateOfBirth = dateOfBirth,
average = average,
major = value
};
}
public StudentDataBuilder Age(int value)
{
return new StudentDataBuilder
{
dateOfBirth = DateTime.Now.AddYears(-value).AddSeconds(1),
average = average,
major = major
};
}
}
Age method deserves a little bit of explanation. Of course, keeping DateTime Date Of Birth is the right thing to do. But from our testing prospective (at the very least) we will be interested in a Student’s current age. If we wouldn’t use the Age method in its current form, we would be forced to mock the Current Date-Time for our test harness, otherwise some of our tests would become invalid with time.
Notice, that in this Fluent Interface implementation the StudentDataBuilder object is immutable. Some other versions pass the same instance throughout all the methods. It may happen, that some testing scenarios can yield weird results in this case so it is better to keep them guaranteed unrelated. In this case we can safely “branch” objects for a different tests:
StudentDataBuilder sdb = StudentDataBuilder.New().Age(20).WithAverage(95);
Student mathStudent = sdb.Taking(Discipline.Math).Build();
Student bilogyStudent = sdb.Taking(DIscipline.Biology).Build();
Thus with just three lines of code we have two completely independent instances which have some fields identical and some different. Test away!
1 comment:
awesome!!
Post a Comment