При использовании метода сборки автоопределения для некоторого типа, как я могу ограничить длину строк, сгенерированных для заполнения этих свойств/полей строки?
AutoFixture - настроить прибор, чтобы ограничить длину генерации строки
Ответ 1
С помощью самого метода Build
существует не так много параметров, но вы можете сделать что-то вроде этого:
var constrainedText =
fixture.Create<string>().Substring(0, 10);
var mc = fixture
.Build<MyClass>()
.With(x => x.SomeText, constrainedText)
.Create();
Однако лично я не вижу, как это лучше или проще понять, что это:
var mc = fixture
.Build<MyClass>()
.Without(x => x.SomeText)
.Create();
mc.SomeText =
fixture.Create<string>().Substring(0, 10);
Лично я очень редко использую метод Build
, так как вместо этого я предпочитаю подход, основанный на соглашениях. При этом существует как минимум три способа ограничения длины строки.
Первый вариант - просто ограничить базу всех строк:
fixture.Customizations.Add(
new StringGenerator(() =>
Guid.NewGuid().ToString().Substring(0, 10)));
var mc = fixture.Create<MyClass>();
Вышеуказанная настройка обрезает все сгенерированные строки до 10 символов. Однако, поскольку алгоритм назначения свойств по умолчанию добавляет имя свойства в строку, конечный результат будет заключаться в том, что mc.SomeText
будет иметь значение, подобное "SomeText3c12f144-5", так что это, вероятно, не то, что вы хотите большую часть времени.
Другой вариант - использовать атрибут [StringLength]
, как указывает Nikos:
public class MyClass
{
[StringLength(10)]
public string SomeText { get; set; }
}
Это означает, что вы можете просто создать экземпляр, явно не указав ничего о длине свойства:
var mc = fixture.Create<MyClass>();
Третий вариант, о котором я могу думать, - мой любимый. Это добавляет специально нацеленное соглашение, в котором говорится, что всякий раз, когда к устройству предлагается создать значение для свойства с именем "SomeText" и строкой типа, результирующая строка должна быть ровно 10 символов:
public class SomeTextBuilder : ISpecimenBuilder
{
public object Create(object request, ISpecimenContext context)
{
var pi = request as PropertyInfo;
if (pi != null &&
pi.Name == "SomeText" &&
pi.PropertyType == typeof(string))
return context.Resolve(typeof(string))
.ToString().Substring(0, 10);
return new NoSpecimen();
}
}
Использование:
fixture.Customizations.Add(new SomeTextBuilder());
var mc = fixture.Create<MyClass>();
Красота этого подхода заключается в том, что он оставляет только SUT и по-прежнему не влияет на другие строковые значения.
Вы можете обобщить этот SpecimenBuilder
на любой класс и длину, например:
public class StringPropertyTruncateSpecimenBuilder<TEntity> : ISpecimenBuilder
{
private readonly int _length;
private readonly PropertyInfo _prop;
public StringPropertyTruncateSpecimenBuilder(Expression<Func<TEntity, string>> getter, int length)
{
_length = length;
_prop = (PropertyInfo)((MemberExpression)getter.Body).Member;
}
public object Create(object request, ISpecimenContext context)
{
var pi = request as PropertyInfo;
return pi != null && AreEquivalent(pi, _prop)
? context.Create<string>().Substring(0, _length)
: (object) new NoSpecimen(request);
}
private bool AreEquivalent(PropertyInfo a, PropertyInfo b)
{
return a.DeclaringType == b.DeclaringType
&& a.Name == b.Name;
}
}
Использование:
fixture.Customizations.Add(
new StringPropertyTruncateSpecimenBuilder<Person>(p => p.Initials, 5));
Ответ 2
Если максимальная длина является ограничением и у вас есть исходный код для типа, вы можете использовать класс StringLengthAttribute, чтобы указать максимальную длину символов, которые допускается.
Начиная с версии 2.6.0, AutoFixture поддерживает DataAnnotations и автоматически генерирует строку с указанной максимальной длиной.
В качестве примера,
public class StringLengthValidatedType
{
public const int MaximumLength = 3;
[StringLength(MaximumLength)]
public string Property { get; set; }
}
[Fact]
public void CreateAnonymousWithStringLengthValidatedTypeReturnsCorrectResult()
{
// Fixture setup
var fixture = new Fixture();
// Exercise system
var result = fixture.CreateAnonymous<StringLengthValidatedType>();
// Verify outcome
Assert.True(result.Property.Length <= StringLengthValidatedType.MaximumLength);
// Teardown
}
Вышеприведенный тест также пройдет при использовании Build (для настройки алгоритма создания для одного объекта):
var result = fixture.Build<StringLengthValidatedType>().CreateAnonymous();
Ответ 3
Здесь построит образец, который может генерировать случайные строки произвольной длины - даже дольше, чем строки Guid + PropertyName, которые по умолчанию. Кроме того, вы можете выбрать подмножество символов, которые хотите использовать, и даже пройти в своем собственном случайном порядке (чтобы вы могли контролировать семя, если вам нужно)
public class RandomStringOfLengthRequest
{
public RandomStringOfLengthRequest(int length) : this(length, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890 !?,.-")
{
}
public RandomStringOfLengthRequest(int length, string charactersToUse): this(length, charactersToUse, new Random())
{
}
public RandomStringOfLengthRequest(int length, string charactersToUse, Random random)
{
Length = length;
Random = random;
CharactersToUse = charactersToUse;
}
public int Length { get; private set; }
public Random Random { get; private set; }
public string CharactersToUse { get; private set; }
public string GetRandomChar()
{
return CharactersToUse[Random.Next(CharactersToUse.Length)].ToString();
}
}
public class RandomStringOfLengthGenerator : ISpecimenBuilder
{
public object Create(object request, ISpecimenContext context)
{
if (request == null)
return new NoSpecimen();
var stringOfLengthRequest = request as RandomStringOfLengthRequest;
if (stringOfLengthRequest == null)
return new NoSpecimen();
var sb = new StringBuilder();
for (var i = 0; i < stringOfLengthRequest.Length; i++)
sb.Append(stringOfLengthRequest.GetRandomChar());
return sb.ToString();
}
}
Затем вы можете использовать его для заполнения свойства объекта следующим образом:
var input = _fixture.Build<HasAccountNumber>()
.With(x => x.AccountNumber,
new SpecimenContext(new RandomStringOfLengthGenerator())
.Resolve(new RandomStringOfLengthRequest(50)))
.Create();
Ответ 4
Я добавил в проект собственный построитель строк. Он добавляет 4-значное число вместо указателя.
public class StringBuilder : ISpecimenBuilder
{
private readonly Random rnd = new Random();
public object Create(object request, ISpecimenContext context)
{
var type = request as Type;
if (type == null || type != typeof(string))
{
return new NoSpecimen();
}
return rnd.Next(0,10000).ToString();
}
}
Ответ 5
Некоторые из других решений довольно хороши, но если вы создаете объекты в тестовом устройстве на основе модели данных, есть и другие проблемы, с которыми вы столкнетесь. Во-первых, атрибут StringLength не является отличным вариантом для модели данных, основанной на кодах, поскольку он добавляет кажущиеся дубликаты аннотаций. Непонятно, почему вам нужны как StringLength, так и MaxLength. Сохранение их в синхронизации вручную является излишним.
Я бы наклонился к настройке того, как работает Fixture.
1) Вы можете настроить прибор для класса и указать, что при создании этого свойства вы обрезаете строку, если необходимо. Итак, чтобы обрезать FieldThatNeedsTruncation в MyClass до 10 символов, вы должны использовать следующее:
fixture.Customize<MyClass>(c => c
.With(x => x.FieldThatNeedsTruncation, Fixture.Create<string>().Substring(0,10));
2) Проблема с первым решением заключается в том, что вам все равно нужно синхронизировать длину, только теперь, вероятно, вы делаете это в двух совершенно разных классах, а не в двух строках последовательных аннотаций данных.
Второй вариант, который я придумал для генерации данных из произвольной модели данных без необходимости вручную устанавливать его в каждой пользовательской настройке, которую вы объявляете, заключается в использовании настраиваемого ISpecimenBuilder, который напрямую вычисляет MaxLengthAttribute. Здесь исходный код для класса, который я модифицировал из самой библиотеки, которая оценивала атрибут StringLengthAttribute.
/// <summary>
/// Examine the attributes of the current property for the existence of the MaxLengthAttribute.
/// If set, use the value of the attribute to truncate the string to not exceed that length.
/// </summary>
public class MaxLengthAttributeRelay : ISpecimenBuilder
{
/// <summary>
/// Creates a new specimen based on a specified maximum length of characters that are allowed.
/// </summary>
/// <param name="request">The request that describes what to create.</param>
/// <param name="context">A container that can be used to create other specimens.</param>
/// <returns>
/// A specimen created from a <see cref="MaxLengthAttribute"/> encapsulating the operand
/// type and the maximum of the requested number, if possible; otherwise,
/// a <see cref="NoSpecimen"/> instance.
/// Source: https://github.com/AutoFixture/AutoFixture/blob/ab829640ed8e02776e4f4730d0e72ab3cc382339/Src/AutoFixture/DataAnnotations/StringLengthAttributeRelay.cs
/// This code is heavily based on the above code from the source library that was originally intended
/// to recognized the StringLengthAttribute and has been modified to examine the MaxLengthAttribute instead.
/// </returns>
public object Create(object request, ISpecimenContext context)
{
if (request == null)
return new NoSpecimen();
if (context == null)
throw new ArgumentNullException(nameof(context));
var customAttributeProvider = request as ICustomAttributeProvider;
if (customAttributeProvider == null)
return new NoSpecimen();
var maxLengthAttribute = customAttributeProvider.GetCustomAttributes(typeof(MaxLengthAttribute), inherit: true).Cast<MaxLengthAttribute>().SingleOrDefault();
if (maxLengthAttribute == null)
return new NoSpecimen();
return context.Resolve(new ConstrainedStringRequest(maxLengthAttribute.Length));
}
}
Затем просто добавьте его в качестве настройки, как показано ниже:
fixture.Customizations.Add(new MaxLengthAttributeRelay());
Ответ 6
Примечание. Это решение на самом деле не использует AutoFixture, но иногда сложнее использовать пакет, чем просто программировать его самостоятельно.
Зачем использовать AF, когда использовать AF труднее и уродливее, я предпочитаю использовать:
var fixture = new Fixture();
fixture.Create<string>(length: 9);
Поэтому я создал метод расширения:
public static class FixtureExtensions
{
public static T Create<T>(this IFixture fixture, int length) where T : IConvertible, IComparable, IEquatable<T>
{
if (typeof(T) == typeof(string))
{
// there are some length flaws here, but you get the point.
var value = fixture.Create<string>();
if (value.Length < length)
throw new ArgumentOutOfRangeException(nameof(length));
var truncatedValue = value.Substring(0, length);
return (T)Convert.ChangeType(truncatedValue, typeof(T));
}
// implement other types here
throw new NotSupportedException("Only supported for strings (for now)");
}
}
Ответ 7
Вот мое решение и примечания.
Во-первых, ясно, что в AutoFixture есть некоторая тесная связь. Создайте знания о том, как образец создается и настраивается. Для строк это раздражает, потому что мы знаем, что по умолчанию используется Guid. Используя эти знания, я создал Func, который обрабатывает это в моих тестовых примерах:
private readonly Func<IFixture, int, string> _createString = (IFixture fixture, int length) => (fixture.Create<string>() + fixture.Create<string>()).Substring(0, length);
Это может быть определено индуктивно, чтобы использовать guid, сгенерированный Auto-Fixture по умолчанию. По умолчанию это 36 символов, поэтому:
private readonly Func<IFixture, int, string> _createString = (IFixture fixture, int length) =>
{
if (length < 0) throw new ArgumentOutOfRangeException(nameof(length));
var sb = new StringBuilder();
const int autoFixtureStringLength = 36;
var i = length;
do
{
sb.Append(fixture.Create<string>());
i -= autoFixtureStringLength;
} while (i > autoFixtureStringLength && i % autoFixtureStringLength > 0);
sb.Append(fixture.Create<string>());
return (sb).ToString().Substring(0, length);
};
Опять же, вся предпосылка этого решения заключается в том, что AutoFixture уже тесно связана с любой политикой создания объектов, которая у вас есть. Все, что ты делаешь, это увлекаешься этим.
Вероятно, было бы идеально, если бы AutoFixture выставлял точку запроса "минимальное значение" и "максимальное значение" для запроса. Это то, что делают фреймворки функционального тестирования, такие как QuickCheck, и затем позволяют "уменьшить" значение.