Связывание настроек с помощью Autofixture

Я знаю, что Autofixture прекращает создание объекта, когда находит ISpecimenBuilder, который может удовлетворить запрос. Поэтому, когда я применяю несколько последовательных настроек, все, кроме последней, игнорируются. Как мне вместо этого объединить настройки? Другими словами, как мне изменить этот фрагмент:

fixture.Customize<SomeClass>(ob => ob.With(x => x.Id, 123)); // this customization is ignored
fixture.Customize<SomeClass>(ob => ob.With(x => x.Rev, 4341)); // only this one takes place

Чтобы быть эквивалентным этому фрагменту:

fixture.Customize<SomeClass>(ob => ob
    .With(x => x.Id, 123)
    .With(x => x.Rev, 4341)); // both customization are applied

person MnZrK    schedule 26.02.2015    source источник
comment
Короткий ответ заключается в том, что вы не можете сделать это из коробки с помощью AutoFixture, и что это сделано намеренно. Когда-то, много лет назад, у меня действительно был прототип, который вел себя так, как вы хотели бы, вместо того, чтобы перезаписывать предыдущую настройку, но, попробовав его в течение некоторого времени, я пришел к выводу, что это слишком сбивает с толку и может привести к некоторому трудному для понимания поведению, когда все станет более сложным. За многие годы, которые я использовал AutoFixture, у меня никогда не было необходимости в этом, поэтому мне любопытно узнать о сценарии использования.   -  person Mark Seemann    schedule 26.02.2015
comment
@MarkSeemann Вариант использования - применить гибкий интерфейс. Так что вместо чего-то вроде fixture.SetupWithEmailAndCache () я мог бы использовать fixture.WithEmail (). WithCache (). Как включение электронной почты, так и включение кеша требует настройки определенных свойств объекта конфигурации, а также некоторых других действий. Но эти свойства и действия не пересекаются для электронной почты и кеша, поэтому нет смысла объединять такую ​​настройку в одну функцию.   -  person MnZrK    schedule 27.02.2015
comment
Спасибо за разъяснения. Если вам обычно необходимо решать независимые проблемы в одном и том же тестовом коде, что это говорит вам о вашей тестируемой системе?   -  person Mark Seemann    schedule 27.02.2015
comment
@MarkSeemann Я не вижу в этом ничего плохого - как еще вы можете протестировать модуль, который зависит от других модулей, которые независимы друг от друга? Что не так в моем примере, так это то, что, хотя я заявляю, что мои электронная почта и кеш независимы, у них есть что-то общее - и это объект конфигурации. Это нарушает принцип единой ответственности и приводит к проблемам с тестированием. Другой пример - у меня есть настройка, общая для всего набора тестов; только в одном тестовом случае я хочу немного изменить настройку, но для этого мне нужно будет скопировать всю настройку.   -  person MnZrK    schedule 27.02.2015


Ответы (1)


Вот что я придумал:

public class CustomFixture : Fixture
{
    public CustomFixture ()
    {
        this.Inject(this);
    }

    private readonly List<object> _transformations = new List<object>();

    public void DelayedCustomize<T>(Func<ICustomizationComposer<T>, ISpecimenBuilder> composerTransformation)
    {
        this._transformations.Add(composerTransformation);
    }

    public void ApplyCustomize<T>()
    {
        this.Customize<T>(ob =>
        {
            return this._transformations.OfType<Func<ICustomizationComposer<T>, ISpecimenBuilder>>()
                .Aggregate(ob, (current, transformation) => (ICustomizationComposer<T>)transformation(current));
        });
    }

}

И использование:

var fixture = new CustomFixture();

fixture.DelayedCustomize<SomeClass>(ob => ob.With(x => x.Id, 123)); 
fixture.DelayedCustomize<SomeClass>(ob => ob.With(x => x.Rev, 4341)); 
fixture.ApplyCustomize<SomeClass>();

var obj = fixture.Create<SomeClass>();
// obj.Id == 123
// obj.Rev == 4341

Не идеально из-за необходимости ApplyCustomize.

person MnZrK    schedule 26.02.2015