Может ли AutoFixture генерировать случайные строки/текст из предоставленного набора данных?

Можно использовать AutoFixture для генерации случайных данных для строкового свойства... но это из фиксированного источника данных?

Например: у меня есть 30 названий улиц, жестко запрограммированных в коллекцию памяти (массив/список/что угодно). Затем для моего экземпляра Address свойство StreetName является не просто случайным строковым значением (которое является результатом AutoFixture по умолчанию), а одним из названий улиц из этой жестко закодированной коллекции.

Моей первой мыслью было использовать случайное число, которое может создать AutoFixture.. и это число находится внутри длины/размера массива... поэтому, по сути, я рандомизирую слот массива. Затем, используя это случайное число, получите значение (также известное как название улицы) слота коллекции/массива (т. е. с учетом индексатора получите значение в этом местоположении индекса).

Это как надо делать?


person Pure.Krome    schedule 08.02.2016    source источник


Ответы (1)


Как и во многих других случаях, связанных с AutoFixture, все становится намного проще, если вы можете использовать более явное моделирование предметной области. Вместо того, чтобы моделировать StreetName как string, введите для него объект предметной области:

public sealed class StreetName
{
    private readonly string value;

    public StreetName(string streetName)
    {
        value = streetName ?? throw new ArgumentNullException(nameof(streetName));
    }

    public override bool Equals(object obj)
    {
        var other = obj as StreetName;
        if (other == null)
            return base.Equals(obj);

        return Equals(value, other.value);
    }

    public override int GetHashCode()
    {
        return value.GetHashCode();
    }

    public override string ToString()
    {
        return value;
    }

    public static implicit operator string(StreetName streetAddress)
    {
        return streetAddress.value;
    }

    public static implicit operator StreetName(string streetAddress)
    {
        return new StreetName(streetAddress);
    }
}

Это один из тех шагов моделирования, которые болезненны в C# и Java, но будут однострочными в F# или Haskell...

Предположим, однако, что у нас есть список предопределенных названий улиц:

public static class StreetNames
{
    public static IEnumerable<string> Values = new[] {
        "221 B Baker St.",
        "1313 Webfoot Walk",
        "420 Paper St.",
        "42 Wallaby Way"
        /* More addresses go here... */ };
}

Теперь вы можете тривиально указать AutoFixture выбирать только из этого списка, используя ElementsBuilder:

var fixture = new Fixture();
fixture.Customizations.Add(
    new ElementsBuilder<StreetName>(StreetNames.Values.Select(s => (StreetName)s)));

Однако на данном этапе это означает, что когда вы создаете значения StreetName с помощью AutoFixture, он будет выбирать из StreetNames.Values, но по-прежнему не будет этого делать, когда вы попросите его создать значения Address. Вы можете решить (ха-ха) эту проблему с помощью небольшого ISpecimenBuilder:

public class StreetNameBuilder : ISpecimenBuilder
{
    public object Create(object request, ISpecimenContext context)
    {
        var pi = request as PropertyInfo;
        if (pi == null || pi.Name != "StreetName" || pi.PropertyType != typeof(string))
            return new NoSpecimen();

        var sn = context.Resolve(typeof(StreetName));
        return (string)(StreetName)sn;
    }
}

Теперь вы можете настроить свой Fixture следующим образом:

var fixture = new Fixture();
fixture.Customizations.Add(
    new ElementsBuilder<StreetName>(StreetNames.Values.Select(s => (StreetName)s)));
fixture.Customizations.Add(new StreetNameBuilder());

Теперь он создаст Address значений из StreetName значений, выбранных из предопределенного списка.

Если вы не можете изменить свою модель домена, вы все равно можете добавить класс, например StreetName. Просто добавьте его в базу тестового кода, а не в базу производственного кода.

person Mark Seemann    schedule 21.02.2018
comment
Почему нельзя просто добавить функцию с именем One.Of(new {"Buy","Sell"}) или One.Of(params object[] string, чтобы она была просто One.Of("Buy","Sell") или One.Of(Enumerable.Range(1, 50))? Я думаю, что ваш ответ слишком сложен для того, о чем спрашивает пользователь, и пользователь дает вам хорошие идеи о том, как упростить использование AutoFixture. Обычно нам нужны коллекции, построенные из следующих комбинаций: не более одного элемента является конкретным значением, все элементы уникальны, все элементы являются подмножествами какого-то другого набора элементов, все элементы являются уникальными подмножествами какого-то другого набора элементов. набор элементов и т.д. - person John Zabroski; 02.06.2020
comment
Я вижу, вы написали этот ответ 10 месяцев назад: twitter.com/AutoFixture/status/1072235866205118464 - но похоже, что оставшийся запрос состоит в том, чтобы разрешить One.Of возвращать ElementBuilder<T>, построенный с выражением генерации значения, аналогично тому, что предлагает GenFu/ObjectHydrator? - person John Zabroski; 02.06.2020
comment
@JohnZabroski Я передал AutoFixture много лет назад. Пожалуйста, направляйте комментарии и вопросы текущим сопровождающим. - person Mark Seemann; 02.06.2020
comment
Спасибо за быстрый ответ. Готово. github.com/AutoFixture/AutoFixture/issues/1179 - person John Zabroski; 02.06.2020