5

On our last project we ended up with a shared test fixture for our unit tests which gave a lot of problems. So on our current project I've looked into the builder pattern. We run our unit tests in memory on the development machines and against the database on the build server.

Currently I have a T4 template which produces for example the following builder for a Student:

public class StudentBuilder : Builder<Student, StudentBuilder>
{
    public StudentBuilder()
    {
        IsMale = true;
    }

    public StudentBuilder WithFirstName(string firstName)
    {
        this.FirstName = firstName;
        return this;
    }

    public StudentBuilder WithLastName(string lastName)
    {
        this.LastName = lastName;
        return this;
    }

    public StudentBuilder WithIsMale(bool isMale)
    {
        this.IsMale = isMale;
        return this;
    }

    internal override Student Construct()
    {
        Student result = new Student()
        {
            FirstName = FirstName ?? "FirstName:" + id.ToString(),
            LastName = LastName ?? "LastName:" + id.ToString(),
            IsMale = IsMale,
            Id = id,
        };

     /   return result;
    }
}

Trough the base classes I can use this in the following way:

Student wouter = StudentBuilder.Build()
    .WithFirstName("Wouter")
    .WithLastName("de Kort");
List<Student> students = StudentBuilder.Build().Multiple(10, (builder, index) => builder.WithFirstName("FirstName" + index));

We run integration tests on our build server to make sure everything works against the database. This means we have to make sure that all referential constrains are met. But then the problems begin.

For example, a student is required to have a mentor, a mentor belongs to a school, a school to a city, a city to a ....

This would result in code like:

StudentBuilder.Build().WithMentor(MentorBuilder.Build().WithSchool(SchoolBuilder.Build().WithCity(CityBuilder.Build()))

How should I optimize this? I've thought about doing the 'default building' in the Construct method of each Builder but if I would build 10 students then it would lead to 10 mentors in 10 schools in 10 cities in 10....

Or maybe creating methods like WithAllCity(..), WithAll(School)

Any ideas? Am I actually using the Builder Pattern the right way? Could a Director class help? Or should I have inherited classes from StudentBuilder which solve these different cases?

Or another idea, should I add more validation in my service layer before sending the data to the database? Then I would catch more errors in my unit tests against the in memory database.

Wouter de Kort
  • 39,090
  • 12
  • 84
  • 103
  • `We run our unit tests in memory on the development machines and against the database on the build server.` - That sounds painful to troubleshoot. How is it working out for your project? – Ritch Melton Nov 09 '11 at 17:39
  • I'm using http://visualstudiogallery.msdn.microsoft.com/69023d00-a4f9-4a34-a6cd-7e854ba318b5 to create different app.config settings with a unity section to switch from in memory to database. The developers can let there unit tests target the database by switching there build configuration. This speeds up development (in memory tests run really fast) but also catches the bugs that the in memory fake doesn't catch. But there you immediately have the problem why I'm asking this question. A really long builder setup to make sure the tests pas the build server – Wouter de Kort Nov 09 '11 at 17:56

2 Answers2

1

If you are going to building lists of students you can make a list builder class - StudentsBuilder. By default the builder class will generate a list of Students will psuedo-random properties defined by you. This is similar to the approach of AutoPoco.

I find that making your own list builder class is more flexible in terms of defining the creation behavior and supporting any type of class. I make a builder class with IList<T> fields (similar to a data-oriented structure of arrays (SoA) approach).

public class StudentsBuilder
{
    private int _size;
    private IList<string> _firstNames; 
    private IList<string> _lastNames;
    private IList<MentorBuilder> _mentors;

    public StudentsBuilder(int size = 10)
    {
        _size = 10;
        _firstNames = new RandomStringGenerator(size).Generate();
        _lastNames = new RandomStringGenerator(size).Generate();
        _mentors = Enumerable.Range(0, size).Select(_ => new MentorBuilder()).ToList();
    }

    public StudentsBuilder WithFirstNames(params string[] firstNames)
    {
        _firstNames = firstNames;
        return this;
    }

    public IList<Student> Build()
    {
        students = new List<Student>();
        for (int i = 0; i < size; i++)
            students.Add(new Student(_firstNames[i], _lastNames[i], _mentors[i].Build());
        return students;
    }
}

Each field list is overridden using a separate method taking a params array argument. You could also make field lists public in order to use a fancier With(Action<StudentsBuilder> action) syntax for overriding values. Test code looks like:

var students = new StudentBuilder(size: 4)
    .WithFirstNames("Jim", "John", "Jerry", "Judy")
    .Build();
rybo103
  • 335
  • 2
  • 7
1

If your unit test is going to be using the student's mentor, the mentor's school, and the school's city, I think it is reasonable for the unit test to have code to build all of that, but I suggest your unit test might not be testing just one thing. Make your unit tests more specific so that they are not drilling down through so many properties.

If the problem is not your unit tests, but that your student class demands a mentor to be fed into its constructor, and that mentor cannot be null, consider relaxing that requirement to allow a null mentor (my preference I suppose), or make the builder fill in a "default" object as you say. You could even make your default objects throw exceptions if you try to access their properties, prompting you that your unit test needs you to build an "actual" object.

Joe Daley
  • 45,356
  • 15
  • 65
  • 64
  • The problem is we are running our unit tests also against the real database (integration tests on the build server) and the datamodel requires those properties set. This is also because sometimes for example people forget to initialize a DateTime which throws an error in SQL Server but not in memory. – Wouter de Kort Nov 09 '11 at 09:32
  • 1
    Separate your unit and integration tests. Keep the unit tests (in-memory) specific. Keep the integration tests (with database) general, and use a full data set that is shared among multiple tests (which, I know, is exactly what you are trying to avoid). There are some good suggestions at http://stackoverflow.com/questions/482827 to make using shared test data easier, but I don't think you should try to avoid it altogether in integration tests. Sorry if this is a non-answer :) – Joe Daley Nov 09 '11 at 09:53
  • I'm testing Presenters which Services which accesses repositories. Integration tests use WCF and Entity Framework so these tests take long to run. But they do catch errors that our unit tests itself won't find especially in the database itself. So I have to unit test and integration test my presenters and the test code is exactly the same (which we have setup now with some dependency injection and a fake ObjectContext in memory). I can't create separate integration test because that would be a duplicate of my unit tests but much, much slower (which killed using unit test at our last project) – Wouter de Kort Nov 09 '11 at 11:32
  • @Wouter - I don't see mention of the use of Mocks in your explanations. Are you avoiding them for some reason. I think Joe's advice is fairly spot-on. – Ritch Melton Nov 09 '11 at 17:42
  • @RitchMelton I could add it but I think my story is getting a little long (or not??). The infrastructure for the unit tests runs without WCF and against an in memory fake ObjectContext. At the build server the unit tests run against the real database and over WCF to catch any integration errors the unit tests have missed – Wouter de Kort Nov 09 '11 at 17:52