Autofixture: Пожелание: добавить метод клонирования в IFixture

Созданный на 24 сент. 2020  ·  9Комментарии  ·  Источник: AutoFixture/AutoFixture

Вступление

Я бы хотел, чтобы в IFixture был метод Clone или что-то подобное для создания копии прибора.

На данный момент я могу создать для этого метод расширения:

public static IFixture Clone( this IFixture fixture)
{
   var cloneFixture = new Fixture();

   cloneFixture.Behaviors.Clear();
   foreach ( var behavior in fixture.Behaviors)
   {
      cloneFixture.Behaviors.Add( behavior );
   }

   cloneFixture.Customizations.Clear();
   foreach ( var customization in fixture.Customizations )
   {
      cloneFixture.Customizations.Add( customization );
   }

   cloneFixture.OmitAutoProperties = fixture.OmitAutoProperties;
   cloneFixture.RepeatCount = fixture.RepeatCount;

   cloneFixture.ResidueCollectors.Clear();
   foreach ( var residueCollector in fixture.ResidueCollectors )
   {
      cloneFixture.ResidueCollectors.Add( residueCollector );
   }

   return cloneFixture;
}

Но я считаю, что такая возможность делать копию должна быть в самом проекте.

Подробности

Мой сценарий: я создаю пару экземпляров класса с большим количеством параметров в тесте. Кроме того, я хочу, чтобы параметр конструктора был определенным значением для одного экземпляра и другим значением конструктора для другого экземпляра. Есть несколько способов сделать это; как я это делал:

fixture.Customizations.Add( new FilteringSpecimenBuilder(
            new FixedBuilder( mediaId ),
            new ParameterSpecification( 
                typeof( ObjectId ), 
                "mediaId" ) ) );
var mediaVM = fixture.Build<MediaViewModel>()   
                     .With( mvm => mvm.ParentMixerId, mixerId )
                     .With( mvm => mvm.TrackId, trackId )
                     .Create();

_ = fixture.Customizations.Remove( fixture.Customizations.Last() );

//...

Причина удаления настройки состоит в том, что я пытался, не думая, что последняя добавленная настройка будет иметь более высокий приоритет и будет использоваться; но это было не так.

Если я попытаюсь упростить это с помощью какого-то метода расширения, например:

public static IFixture AddCustomization<T>( this IFixture fixture, T value, string name )
{
   fixture.Customizations.Add( new FilteringSpecimenBuilder(
         new FixedBuilder( value ),
         new ParameterSpecification(
            typeof( T ),
            name ) ) );
   return fixture;
}

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

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

Также будет работать возможность добавить настройку, которая автоматически удаляется после создания объекта.

Надеюсь, все это имеет смысл.

Спасибо, что рассмотрели мою проблему: smiley:

feature request

Самый полезный комментарий

@ajorians Я собирался закрыть проблему из-за бездействия, но не мог пройти мимо этого гигантского конструктора.
Я должен спросить, не думали ли вы изменить свой дизайн?

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

Прежде всего, вы смешиваете концепции новых и вводимых сущностей.
Самое простое решение - переместить ваши инъекционные зависимости (также известные как сервисные интерфейсы) в свойства и внедрить их с помощью инъекции свойств.
Это решит вашу непосредственную проблему копирования Fixture и сделает конструктор более управляемым.

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

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

Позвольте мне знать, если это помогает.

Все 9 Комментарий

@ajorians Добрый день! Насколько я понял ваш пример, похоже, что у нас уже есть то, что вам нужно. Метод Build() фактически создает неизменяемую копию внутреннего графа, поэтому вы можете применять «разовые» настройки поверх AutoFixture, не сохраняя их.

Просмотрите эту демонстрацию игровой площадки:

`` С #
модель открытого класса
{
public int Id {получить; установленный; }
общедоступная строка Имя {получить; установленный; }
}

[Факт]
public void Demo ()
{
var fixture = new Fixture ();
приспособление.(c => c. с (m => m.Id, 42));

var value1 = fixture.Create<Model>();
Assert.Equal(42, value1.Id);
Assert.NotNull(value1.Name);

var value2 = fixture.Build<Model>()
    .With(m => m.Id, (int genInt) => genInt * 2 /* make it always even */)
    .Without(x => x.Name)
    .Create();
Assert.NotEqual(42, value2.Id);
Assert.Equal(value2.Id % 2, 0);
Assert.Null(value2.Name);

var value3 = fixture.Create<Model>();
Assert.Equal(42, value3.Id);
Assert.NotNull(value3.Name);

}
`` ''

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

Спасибо!

Привет @zvirja!

Добрый день!

Если я могу; наш класс Model не будет иметь set и может быть установлен только через конструктор. Итак, если бы вы заменили его на это:

public class Model
{
   public Model( int id, string name )
   {
      Id = id;
      Name = name;
   }

   public int Id { get; }
   public string Name { get; }
}

Извините за то, что не указал это изначально.

Итак, насколько я понимаю, мне нужно добавить настройки, чтобы сделать параметры конструктора. И эти настройки перед Build() .

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

Надеюсь, это имеет смысл. Спасибо за помощь! : смайлик:

@ajorians Правильно ли я понимаю вашу проблему, что у вас есть модель с большим количеством входных аргументов, поэтому вы хотите настроить только один из них, не беспокоясь об этом явно. Верно?

В противном случае вы всегда можете написать что-то вроде
c# var value2 = fixture.Build<Model>() .FromFactory((int id, string name) => new Model(id, name)) .Create();

К сожалению, он не очень хорошо масштабируется с растущим числом параметров 😟

@ajorians Правильно ли я понимаю вашу проблему, что у вас есть модель с большим количеством входных аргументов, поэтому вы хотите настроить только один из них, не беспокоясь об этом явно. Верно?

Да, именно так.

К сожалению, он не очень хорошо масштабируется с растущим числом параметров 😟

К счастью, Freeze работает для многих наших классов с растущим числом параметров. Но да, класс, который я имел в виду, где я использую настройки для создания параметров конструктора, в настоящее время имеет более 30 параметров:
image

И, вероятно, вырастет еще на 4 или около того до конца этого года.

Я понимаю, что это может быть необычно.

Но да, у меня есть большое количество растущих аргументов конструктора ввода, и я хотел бы настроить только один (или несколько) из них, не задумываясь об остальных. Без регистрации или замораживания этого типа на весь тест. Вкратце, добавление настройки - это примерно 5 строк кода.

К счастью, я могу все это делать; но способ, которым я это сделал, включал создание метода расширения clone в моем клиентском коде.

Надеюсь, все это имеет смысл. Спасибо, что посмотрели на это вместе со мной: smiley:

@ajorians Я собирался закрыть проблему из-за бездействия, но не мог пройти мимо этого гигантского конструктора.
Я должен спросить, не думали ли вы изменить свой дизайн?

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

Прежде всего, вы смешиваете концепции новых и вводимых сущностей.
Самое простое решение - переместить ваши инъекционные зависимости (также известные как сервисные интерфейсы) в свойства и внедрить их с помощью инъекции свойств.
Это решит вашу непосредственную проблему копирования Fixture и сделает конструктор более управляемым.

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

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

Позвольте мне знать, если это помогает.

Привет @aivascu!

@ajorians Я собирался закрыть вопрос из-за бездействия

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

Я должен спросить, не думали ли вы изменить свой дизайн?

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

Позвольте мне знать, если это помогает.

Все это действительно помогает. Я сообщу команде, что даже люди в мире думают, что нам нужно подумать о внесении изменений.

Но если я могу спросить, разумно ли для IFixture использовать Clone или аналогичный метод? Или как-то разрешить людям добавить настройку, а затем pop отключить последнюю добавленную настройку или каким-то образом вернуться в состояние до добавления настройки? Я согласен с тем, что понимание сценария, который побуждает к такому желаемому поведению, важно, и, конечно, этот сценарий, в первую очередь, имеет много проблем. И я действительно вижу, что вы не хотите добавлять дополнительную сложность, если нет хорошего варианта использования. Что ж, ты можешь выбирать, что будет дальше, но если я могу помочь, просто дай мне знать.

Спасибо, что прочитали и обдумали: smiley:

@ajorians мое мнение таково , что мы не должны добавить .Clone() метод на Fixture класса. На мой взгляд, для любого отдельного теста в любой момент времени должна быть только одна тестовая система.

В прошлом было несколько запросов на реализацию функции размораживания / извлечения / однократного замораживания, но никому не удалось ее реализовать.
Я думаю, причина в том, что нет тривиального способа надежно определить, какой конструктор вернет замороженное значение или какое значение было введено последним.

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

public class CountingRelay : ISpecimenBuilder
{
    public CountingRelay(ISpecimenBuilder builder, int maxCount)
    {
        this.MaxCount = maxCount;
        this.Builder = builder;
    }

    public int Count { get; private set; }
    public int MaxCount { get; }
    public ISpecimenBuilder Builder { get; }

    public object Create(object request, ISpecimenContext context)
    {
        if (this.Count == this.MaxCount) return new NoSpecimen();
        var result = this.Builder.Create(request, context);
        if (!(result is NoSpecimen)) this.Count++;
        return result;
    }
}

Привет @aivascu!

@ajorians мое мнение таково , что мы не должны добавить .Clone() метод на Fixture класса.

OK. Спасибо, что обдумали.

Что вы можете сделать, так это реализовать реле, которое игнорирует запросы после определенного количества успешных разрешений.

Я взгляну.

Спасибо еще раз! : смайлик:

@ajorians Я создал более формальный запрос функции № 1214 для явного и автоматического сброса настроек. Вы можете отслеживать прогресс этой функции там.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги