Подтвердить что ты не робот

Невозможно понять разницу между Freeze/Inject/Register

Прежде чем начать, я большой поклонник AutoFixture, я все еще нахожусь в основе обучения этому инструменту. Так что спасибо за разработку Autofixture Mr Ploeh и всех участников.

Итак, начнем с моего вопроса.

Согласно AutoFixture/AutoMoq игнорирует введенный экземпляр/замороженный макет

Интересной частью приведенной ссылки является данный код

Mock<ISettings> settingsMock = new Mock<ISettings>();
settingsMock.Setup(s => s.Get(settingKey)).Returns(xmlString);

ISettings settings = settingsMock.Object;
fixture.Inject(settings);

На что Марк отвечает, можно переписать на

fixture.Freeze<Mock<ISettings>>()
       .Setup(s => s.Get(settingKey)).Returns(xmlString);

Похоже, что синтаксический сахар, используя метод "замораживания", - это способ свободно писать интерфейс, создавая макет, конфигурацию и инъекцию в контейнере для автоматической сборки.

После некоторых исследований в Интернете на самом деле существует функциональное различие между Freeze и Inject. Я нашел этот вопрос: https://github.com/AutoFixture/AutoFixture/issues/59 которые указывают на Как заморозить нулевой экземпляр в AutoFixture

Автор ссылки выше описывает метод Freeze следующим образом:

Внутри, Freeze создает экземпляр запрашиваемого типа (например, IPayPalConfiguration), а затем вводит его, чтобы он всегда возвращался этот экземпляр, когда вы запрашиваете его снова

Я понимаю, что когда мы делаем

var customer = fixture.Freeze<Order>();

он всегда будет использовать тот же экземпляр Order, когда наш код запрашивает тип Order. Но что, если я укажу в конструкторе Freeze, что я хочу, чтобы он использовал конкретный экземпляр?

Вот пример небольшого кода:

[Fact]
public void MethodeName()
{
    var fixture = new Fixture().Customize(new AutoMoqCustomization());
    fixture.Freeze<OrderLine>(new OrderLine("Foo"));
    var order = fixture.Create<Order>();
}

public class Order
{
    private readonly OrderLine _line;

    public Order(OrderLine line)
    {
        _line = line;
    }
}
public class OrderLine
{
    private readonly string _name;

    public OrderLine(string name)
    {
        _name = name;
    }
}

Не должно ли имя OrderLine быть равно "Foo" вместо namefe48163a-d5a0-49a5-b349-7b11ba5f804b? В документации по методу замораживания говорится:

<typeparam name="T">The type to freeze.</typeparam>
<param name="fixture">The fixture.</param>
<param name="seed">Any data that adds additional information when creating the anonymous object. Hypothetically, this value might be the value being frozen, but this is not likely.</param>

Почему автор не уверен, когда возвращается значение? Если я укажу, мой экземпляр в конструкторе Freeze, я ожидаю, что autofixture будет использовать этот экземпляр?

затем

Обратите внимание, что оно вряд ли будет использоваться как замороженное значение, если вы не настроили его. Если вы хотите ввести конкретное значение в Fixture, вы должны использовать этот метод вместо этого. `

Кажется, мне нужно настроить параметр seed. Может ли кто-нибудь уточнить? Решение, указанное в документации, заключается в использовании метода Inject. И действительно, он работает в моем примере кода с OrderLine.

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

4b9b3361

Ответ 1

Регистрация и ввод

Когда-то не было Inject и no Freeze; Register управлял кодом.

Тогда существовала перегрузка Register, определенная таким образом:

public static void Register<T>(this IFixture fixture, T item)

Однако ему пришлось разделить API с этим близким родственником:

public static void Register<T>(this IFixture fixture, Func<T> creator)

Создатель AutoFixture подумал, что это хорошо, но, увы: пользователи были поражены путаницей. Самый печально, пользователь мог написать:

fixture.Register(() => universe.LightUp());

но также

fixture.Register(universe.LightUp);

что означает то же самое, потому что universe.LightUp является ссылкой на метод и, таким образом, соответствует делегату.

Однако этот синтаксис выглядит как ссылка на свойство, поэтому, если LightUp был свойством вместо метода, первая перегрузка будет выбрана компилятором.

Это вызвало много путаницы, поэтому перегрузка Register<T>(this IFixture fixture, T item) была переименована в Inject<T>(this IFixture fixture, T item).

Закрепить

У замораживания есть другая история. Давным-давно, когда я все еще использовал AutoFixture в императивном порядке, я заметил, что я неоднократно писал код следующим образом:

var foo = fixture.Create<Foo>();
fixture.Inject(foo);

Поэтому я решил, что это была концепция и назвала ее Freeze. Метод Freeze является только сокращением для этих двух строк кода.

Я ищу вашу помощь, чтобы понять разницу между Freeze, Inject, а также Register, который, согласно исходному коду, просто вызывается методом Inject, но он принимает lambda

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

Это также относится к Freeze, если бы не перегрузка, используемая в OP:

[EditorBrowsable(EditorBrowsableState.Never)]
public static T Freeze<T>(this IFixture fixture, T seed)

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

Ответ 2

Freeze, Inject и Register все настраивают алгоритм создания.

С Inject и Register вы явно указываете, что объект должен быть создан определенным образом, в вашем примере, поставляя new OrderLine("Foo") вручную.

С Freeze вы не укажете, как должен быть создан объект - вы попросите AutoFixture предоставить вам экземпляр.

В конце все вышеупомянутые методы используют один и тот же API нижнего уровня:

fixture.Customize<T>(c => c.FromFactory(creator).OmitAutoProperties());


Причина, по которой fixture.Freeze<OrderLine>(new OrderLine("Foo")); не создает экземпляр OrderLine с указанным начальным значением, потому что по умолчанию семя игнорируется.

Чтобы поддержать значения семян определенного типа, вы можете создать SeedFavoringRelay<T>:

public class SeedFavoringRelay<T> : ISpecimenBuilder where T : class
{
    public object Create(object request, ISpecimenContext context)
    {
        if (context == null)
            throw new ArgumentNullException("context");

        var seededRequest = request as SeededRequest;
        if (seededRequest == null || !seededRequest.Request.Equals(typeof(T)))
            return new NoSpecimen(request);

        var seed = seededRequest.Seed as T;
        if (seed == null)
            return new NoSpecimen(request);

        return seed;
    }
}

Затем вы можете использовать его, как показано ниже:

fixture.Customizations.Add(
    new SeedFavoringRelay<OrderLine>());

fixture.Freeze<OrderLine>(new OrderLine("Foo"));
// -> Now fixture.Create<Order>() creates an Order with OrderLine Name = "Foo".

Ответ 3

Я изменил ваш тест (который в настоящее время ничего не утверждает, BTW), и если вы выполните его выполнение, вы увидите OrderLine с "Foo", поскольку его личное значение члена _line вводится в Order.

У меня была другая версия теста, в которой я добавил свойства readonly для OrderLine в Order и Name в OrderLine, чтобы вы могли делать утверждения об этих объектах, но это ни здесь, ни там.

Этот тест устанавливает инструмент с помощью метода FromFactory напрямую, что может быть полезно иногда:

[Fact]
public void MethodName()
{
    var fixture = new Fixture().Customize(new AutoMoqCustomization());
    const string expected = "Foo";
    fixture.Customize<OrderLine>(o => o
        .FromFactory(() =>
            new OrderLine(expected)));
    var order = fixture.Create<Order>();
}