Как эффективно создавать и использовать шаблон компоновщика - PullRequest
5 голосов
/ 09 ноября 2011

В нашем последнем проекте мы получили общий тестовый прибор для наших модульных тестов, который дал много проблем. Итак, в нашем текущем проекте я изучил шаблон сборки. Мы запускаем наши модульные тесты в памяти на компьютерах разработчиков и на базе данных на сервере сборки.

В настоящее время у меня есть шаблон T4, который производит, например, следующий компоновщик для ученика:

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;
    }
}

Через базовые классы я могу использовать это следующим образом:

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

Мы запускаем интеграционные тесты на нашем сервере сборки, чтобы убедиться, что все работает с базой данных. Это означает, что мы должны убедиться, что все ссылочные ограничения выполнены. Но тогда начинаются проблемы.

Например, у ученика должен быть наставник, наставник принадлежит школе, школа - городу, город - ....

Это приведет к коду:

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

Как мне это оптимизировать? Я думал о создании «здания по умолчанию» в методе «Построение» каждого строителя, но если бы я собрал 10 учеников, это привело бы к 10 наставникам в 10 школах в 10 городах в 10 ....

Или, возможно, создание методов, таких как WithAllCity (..), WithAll (School)

Есть идеи? Я на самом деле использую шаблон Builder правильно? Может ли помочь директорский класс? Или я должен был унаследовать классы от StudentBuilder, которые решают эти разные случаи?

Или еще одна идея, должен ли я добавить дополнительную проверку в свой уровень обслуживания перед отправкой данных в базу данных? Тогда я поймал бы больше ошибок в моих модульных тестах против базы данных в памяти.

Ответы [ 2 ]

1 голос
/ 22 августа 2015

Если вы собираетесь создавать списки студентов, вы можете создать список класса строителей - StudentsBuilder.По умолчанию класс строителя сгенерирует список учеников с псевдослучайными свойствами, определенными вами.Это похоже на подход AutoPoco .

Я считаю, что создание собственного списка класса списка более гибко с точки зрения определения поведения создания и поддержки любого типа класса.Я создаю класс конструктора с полями IList<T> (аналогично подходу с ориентированной на данные структурой массивов (SoA)).

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;
    }
}

Каждый список полей переопределяется с помощью отдельного метода, принимающего аргумент массива params.Вы также можете сделать списки полей общедоступными, чтобы использовать причудливый синтаксис With(Action<StudentsBuilder> action) для переопределения значений.Тестовый код выглядит так:

var students = new StudentBuilder(size: 4)
    .WithFirstNames("Jim", "John", "Jerry", "Judy")
    .Build();
1 голос
/ 09 ноября 2011

Если в вашем модульном тесте будет использоваться наставник ученика, школа наставника и город школы, я думаю, что для модульного теста разумно иметь код, чтобы построить все это, но я полагаю, что ваш модульный тест может не проверять только одну вещь. Сделайте ваши юнит-тесты более конкретными, чтобы они не проходили сквозь множество свойств.

Если проблема не в ваших модульных тестах, а в том, что ваш ученический класс требует, чтобы наставник был введен в его конструктор, и этот наставник не может быть нулевым, рассмотрите возможность ослабления этого требования, чтобы разрешить нулевого наставника (я полагаю, мое предпочтение), или заставьте застройщика заполнить объект «по умолчанию», как вы говорите. Вы могли бы даже заставить ваши объекты по умолчанию выдавать исключения, если вы пытаетесь получить доступ к их свойствам, предлагая вам, чтобы в вашем модульном тесте вы строили «реальный» объект.

...