Autofixture: Подход к созданию случайного образца на основе настройки

Созданный на 8 янв. 2018  ·  11Комментарии  ·  Источник: AutoFixture/AutoFixture

Привет, я хочу иметь возможность генерировать различные значения на основе ICustomization используя ISpecimenBuilder.CreateMany . Мне было интересно, какое решение будет лучшим, поскольку AutoFixture будет генерировать одинаковые значения для всех объектов.

`` С #
открытый класс FooCustomization: ICustomization
{
public void Customize (прибор IFixture)
{
var specimen = fixture.Build()
.OmitAutoProperties ()
.With (x => x.CreationDate, DateTime.Now)
.With (x => x.Identifier, Guid.NewGuid (). ToString (). Substring (0, 6)) // Должны генерироваться разные значения
.With (x => x.Mail, $ "[email protected]")
.Создавать();

        fixture.Register(() => specimen);
}

}
`` ''

PS .: Основная цель - уменьшить объем кода в моих тестах, поэтому я должен избегать вызова With() для настройки значений для каждого теста. Есть ли правильный способ сделать это?

Обновление: я только что прочитал этот ответ от @ploeh : https://github.com/AutoFixture/AutoFixture/issues/361#issuecomment -86873060, это может быть то, что я ищу.
У этого подхода много недостатков: во-первых, вызов Create<List<Foo>>() кажется действительно нелогичным, потому что это как бы противоречит тому, чего ожидать от CreateMany<Foo> ; который сгенерирует список с жестко заданным размером> (?). Другой недостаток состоит в том, что мне пришлось бы иметь две настройки для каждой сущности; один для создания пользовательских коллекций, а другой - для создания одного экземпляра, поскольку мы переопределяем поведение Create<T> для создания коллекций.

question

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

Это кажется хорошим кандидатом на вопрос о переполнении стека . Нет ничего плохого в том, чтобы задать здесь вопрос, но я думаю, что этот вопрос относится к тому типу, где ответ может принести пользу и другим людям, и легче найти вопросы и ответы на Stack Overflow по сравнению с проблемами GitHub. Во-первых, Google хорошо индексирует вопросы о переполнении стека, тогда как кажется, что он оценивает (закрытые) проблемы GitHub ниже ...

@ploeh Я с тобой согласен ....
Я только что создал вопрос SO, основанный на этой проблеме. Это ссылка для будущих ссылок 👍

@ploeh Спасибо, что

@thiagomajesk На самом деле здесь нет проблем с

@zvirja Спасибо, что разъяснили это 😄

@zvirja Я собираюсь закрыть вопрос о SO на основе отличного ответа
Но я хотел бы попросить немного больше информации о деталях реализации на этом.
Поскольку я не хочу еще больше отклоняться от темы, я думаю, что было бы лучше спросить об этой специфике здесь. Итак, у меня есть такая реализация:

`` С #
открытый класс UniqueShortGuidBuilder: ISpecimenBuilder
{
частная строка только для чтения propName;
частный только для чтения, длина;

public UniqueShortGuidBuilder(Expression<Func<TEntity, string>> expr, int lenght)
{
    propName = ((MemberExpression)expr.Body).Member.Name;
    this.lenght = lenght;
}

общедоступный объект Create (запрос объекта, контекст ISpecimenContext)
{
var pi = запрос как PropertyInfo;

if (pi == null || pi.PropertyType != typeof(string) || pi.Name != propName)
    return new NoSpecimen();

return Guid.NewGuid().ToString().Substring(0, lenght);

}
`` ''

Я ожидал, что при создании множества объектов с помощью AutoFixture ISpecimenBuilder.Create будет вызываться несколько раз, генерируя отдельные Guids для моих объектов; но, видимо, это неправда, и на самом деле они разделяют одно и то же.

Что ж, я только что протестировал ваш код, и он сработал именно так, как хотелось:

`` С #
открытый класс Foo
{
общедоступная строка PropertyToCustomize {получить; установленный; }
}

открытый класс UniqueShortGuidBuilder: ISpecimenBuilder
{
частная строка только для чтения propName;
частный только для чтения, длина;

public UniqueShortGuidBuilder(Expression<Func<TEntity, string>> expr, int lenght)
{
    propName = ((MemberExpression) expr.Body).Member.Name;
    this.lenght = lenght;
}

public object Create(object request, ISpecimenContext context)
{
    var pi = request as PropertyInfo;

    if (pi == null || pi.PropertyType != typeof(string) || pi.Name != propName)
        return new NoSpecimen();

    return Guid.NewGuid().ToString().Substring(0, lenght);
}

}

[Факт]
public void TestCustomization ()
{
var fixture = new Fixture ();
fixture.Customizations.Add (новый UniqueShortGuidBuilder(x => x.PropertyToCustomize, 5));

var manyFoos = fixture.CreateMany<Foo>(10);

var uniqueIds = manyFoos
    .Select(x => x.PropertyToCustomize)
    .Where(x => x.Length == 5)
    .Distinct()
    .ToArray();
Assert.Equal(10, uniqueIds.Length);

}
`` ''

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

@zvirja Это странно. Приведенный ниже код является минимальным воспроизведением проблемы, с которой я столкнулся.
Если я удалю Fixture.Customize(new FooCustomization()); тест будет пройден, в противном случае - нет.

`` С #
используя Систему;
с помощью Microsoft.VisualStudio.TestTools.UnitTesting;
с использованием AutoFixture.Kernel;
using System.Linq.Expressions;
using System.Reflection;
с помощью AutoFixture;
using System.Linq;

пространство имен UnitTestProject1
{
открытый класс Foo
{
общедоступная строка PropertyToCustomize {получить; установленный; }
}

public class FooCustomization : ICustomization
{
    public void Customize(IFixture fixture)
    {
        var specimen = fixture.Build<Foo>()
            .OmitAutoProperties()
            .With(x => x.PropertyToCustomize)
            .Create();

        fixture.Register(() => specimen);
    }
}

public class UniqueShortGuidBuilder<TEntity> : ISpecimenBuilder
{
    private readonly string propName;
    private readonly int lenght;

    public UniqueShortGuidBuilder(Expression<Func<TEntity, string>> expr, int lenght)
    {
        propName = ((MemberExpression)expr.Body).Member.Name;
        this.lenght = lenght;
    }

    public object Create(object request, ISpecimenContext context)
    {
        var pi = request as PropertyInfo;

        if (pi == null || pi.PropertyType != typeof(string) || pi.Name != propName)
            return new NoSpecimen();

        return Guid.NewGuid().ToString().Substring(0, lenght);
    }
}

[TestClass]
public class UnitTest1
{
    public static Fixture Fixture { get; private set; }

    [ClassInitialize]
    public static void ClassInitialize(TestContext context)
    {
        Fixture = new Fixture();
        Fixture.Customizations.Add(new UniqueShortGuidBuilder<Foo>(x => x.PropertyToCustomize, 5));
        Fixture.Customize(new FooCustomization());
    }

    [TestMethod]
    public void TestMethod1()
    {
        var manyFoos = Fixture.CreateMany<Foo>(10);

        var uniqueIds = manyFoos
            .Select(x => x.PropertyToCustomize)
            .Where(x => x.Length == 5)
            .Distinct()
            .ToArray();

        Assert.AreEqual(10, uniqueIds.Length);
    }
}

}
`` ''

Рекомендация: если вы полагаетесь на извлечение первых символов _n_ из строки GUID, не называйте класс UniqueShortGuidBuilder . Значения могут быть довольно уникальными, если длина равна 5, но если вы установите ее на 1, вы, скорее всего, увидите столкновения. В любом случае уникальность не гарантируется.

Если вас интересует только длина строки плюс негарантированная уникальность, почему бы просто не попросить AutoFixture указать число, скажем, от 0 до 99 999, и не превратить его в строку? IIRC, числа уникальны, пока диапазон не исчерпан.

Если я удалю Fixture.Customize (новый FooCustomization ()); тест пройдёт, иначе нет.

Что ж, на самом деле это ожидаемое поведение, если вы посмотрите на ситуацию более внимательно 😅 См. Мой ответ в # 962. Предоставленная реализация FooCustomization создает экземпляр типа Foo и настраивает прибор так, чтобы он всегда возвращал этот экземпляр. Следовательно, когда позже вы создаете несколько экземпляров типа Foo , каждый раз возвращается один и тот же объект и ваш конструктор больше не вызывается.

Если вы хотите, чтобы новый экземпляр создавался каждый раз, когда вы запрашиваете Foo , используйте Customize<> API, как я предлагал здесь .

@ploeh Спасибо за подсказку, но этот код - всего лишь пример для тестирования. В реальных случаях длина не будет слишком короткой, чтобы повлиять на уникальность Guids 😄.

Если вас интересует только длина строки плюс негарантированная уникальность, почему бы просто не попросить AutoFixture указать число, скажем, от 0 до 99 999, и не превратить его в строку?

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

Что ж, на самом деле это ожидаемое поведение, если вы посмотрите на ситуацию более внимательно.

@zvirja Это ясно после вашего ответа в здесь ) 😁.

В заключение благодарим вас за терпение и прекрасные объяснения.
Я уверен, что это будет полезно и для других в будущем 😉.

Хорошо, честно, у меня может быть склонность воспринимать вещи слишком буквально 😄

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

Смежные вопросы

ecampidoglio picture ecampidoglio  ·  7Комментарии

Eldar1205 picture Eldar1205  ·  5Комментарии

tiesmaster picture tiesmaster  ·  7Комментарии

zvirja picture zvirja  ·  4Комментарии

JoshKeegan picture JoshKeegan  ·  6Комментарии