Применение DRY к операторам Autofixture "Build"

Предположим, что у меня есть этот конкретный класс:

public partial class User
{
    public int ID { get; set; }
    public string Email { get; set; }
    public string FullName { get; set; }
}

И я хочу создать анонимный экземпляр с допустимым адресом электронной почты, а поле fullname - не более 20 символов. Я могу это сделать:

var fixture = new Fixture();
var anonUser = fixture.Build<User>()
    .With(x => x.Email, string.Format("{0}@fobar.com", fixture.Create<string>()))
    .With(x => x.FullName,  fixture.Create<string>()Substring(0,20))
    .Create();

Есть ли способ, которым я могу определить это в одном месте, так что AF знает, что я могу получить свой настроенный класс anon, используя:

var newAnon = fixture.Build<User>();

Ответ 1

У вас есть различные варианты. На мой взгляд, лучший вариант - применить GOOS принцип прослушивания ваших тестов. Когда тест становится трудным для записи, пришло время пересмотреть дизайн системного теста (SUT). AutoFixture имеет тенденцию усиливать этот эффект.

Рефакторинг для объектов значения

Если у вас есть требование, чтобы свойства Email и FullName имели особенно ограниченные значения, это может указывать на то, что вместо Primitive Obsession целевой API выиграют от определения явных Email и FullName объектов значения. Пример canonical AutoFixture - это номера телефонов.

Использовать аннотации данных

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

Он может выглядеть примерно так:

public partial class User
{
    public int ID { get; set; }
    [RegularExpression("regex for emails is much harder than you think")]
    public string Email { get; set; }
    [MaxLenght(20)]
    public string FullName { get; set; }
}

Лично мне не нравится этот подход, потому что я предпочитаю правильную инкапсуляцию.

Использовать настройку

Вместо использования метода Build<T> используйте метод Customize<T>:

var fixture = new Fixture();
fixture.Customize<User>(c => c
    .With(x => x.Email, string.Format("{0}@fobar.com", fixture.Create<string>())
    .With(x => x.FullName, fixture.Create<string>().Substring(0,20)));
var newAnon = fixture.Create<User>();

Запись условного Builder

Наконец, вы можете написать условную настройку:

public class EmailSpecimenBuilder : ISpecimenBuilder
{
    public object Create(object request,
        ISpecimenContext context)
    {
        var pi = request as PropertyInfo;
        if (pi == null)
        {
            return new NoSpecimen(request);
        }

        if (pi.PropertyType != typeof(string)
            || pi.Name != "Email")
        {
            return new NoSpecimen(request);
        }

        return string.Format("{0}@fobar.com", context.Resolve(typeof(string)));
    }
}

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