C# 自动夹具-配置夹具以限制字符串生成长度

C# 自动夹具-配置夹具以限制字符串生成长度,c#,autofixture,C#,Autofixture,当对某些类型使用AutoFixture的生成方法时,如何限制生成的字符串长度以填充该对象的字符串属性/字段?如果最大长度是一个约束,并且您拥有该类型的源代码,则可以使用该类指定允许的最大字符长度 从版本2.6.0开始,AutoFixture支持DataAnnotations,它将自动生成指定最大长度的字符串 例如, public class StringLengthValidatedType { public const int MaximumLength = 3; [Stri

当对某些类型使用AutoFixture的生成方法时,如何限制生成的字符串长度以填充该对象的字符串属性/字段?

如果最大长度是一个约束,并且您拥有该类型的源代码,则可以使用该类指定允许的最大字符长度

从版本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
}
公共类StringLengthValidatedType
{
公共常数int最大长度=3;
[StringLength(最大长度)]
公共字符串属性{get;set;}
}
[事实]
public void CreateAnonymousWithStringLengthValidatedTypeReturnsCorrectResult()创建匿名
{
//夹具设置
var fixture=新fixture();
//运动系统
var result=fixture.CreateAnonymous();
//核实结果

Assert.True(result.Property.Length使用
Build
方法本身,没有那么多选项,但您可以这样做:

var constrainedText = 
    fixture.Create<string>().Substring(0, 10);
var mc = fixture
    .Build<MyClass>()
    .With(x => x.SomeText, constrainedText)
    .Create();
        var input = _fixture.Build<HasAccountNumber>()
                            .With(x => x.AccountNumber,
                                  new SpecimenContext(new RandomStringOfLengthGenerator())
                                      .Resolve(new RandomStringOfLengthRequest(50)))
                            .Create();
上述自定义将所有生成的字符串截断为10个字符。但是,由于默认的属性分配算法将属性名称前置到字符串中,因此最终结果将是
mc。SomeText
将具有类似“SomeText3c12f144-5”的值,因此大多数情况下这可能不是您想要的

另一种选择是使用
[StringLength]
属性,正如Nikos指出的:

public class MyClass
{
    [StringLength(10)]
    public string SomeText { get; set; }
}
这意味着您可以只创建一个实例,而无需明确说明有关属性长度的任何内容:

var mc = fixture.Create<MyClass>();
用法:

fixture.Customizations.Add(new SomeTextBuilder());
var mc = fixture.Create<MyClass>();
fixture.Customizations.Add(
    new StringPropertyTruncateSpecimenBuilder<Person>(p => p.Initials, 5));
用法:

fixture.Customizations.Add(new SomeTextBuilder());
var mc = fixture.Create<MyClass>();
fixture.Customizations.Add(
    new StringPropertyTruncateSpecimenBuilder<Person>(p => p.Initials, 5));
fixture.Customizations.Add(
新StringPropertyRuncateSpecimenBuilder(p=>p.首字母缩写,5));

这是一个示例生成器,它可以生成任意长度的随机字符串,甚至比默认情况下的Guid+PropertyName字符串还要长。此外,您还可以选择要使用的字符子集,甚至传入自己的随机字符串(这样,如果需要,您可以控制种子)

公共类RandomStringOfLengthRequest
{
public RandomStringOfLengthRequest(int-length):此(长度,“ABCDEFGHIJKLMNOPQRSTUVWXYZABCDFGHIJKLMNOPQRSTUVWXYZ01234567890!,.-”)
{
}
public RandomStringOfLengthRequest(int-length,string charactersToUse):此(长度,charactersToUse,new Random())
{
}
public RandomStringOfLengthRequest(整数长度、字符串字符使用、随机)
{
长度=长度;
随机=随机;
CharactersToUse=CharactersToUse;
}
公共整数长度{get;私有集;}
公共随机{get;私有集;}
公共字符串字符使用{get;private set;}
公共字符串GetRandomChar()
{
返回CharactersToUse[Random.Next(CharactersToUse.Length)].ToString();
}
}
长度生成器的公共类:ISpecimenBuilder
{
公共对象创建(对象请求,ISPecementContext上下文)
{
if(请求==null)
返回新的NoSpecimen();
var stringOfLengthRequest=请求为RandomStringOfLengthRequest;
if(stringOfLengthRequest==null)
返回新的NoSpecimen();
var sb=新的StringBuilder();
for(var i=0;i
然后可以使用它填充对象的属性,如下所示:

var constrainedText = 
    fixture.Create<string>().Substring(0, 10);
var mc = fixture
    .Build<MyClass>()
    .With(x => x.SomeText, constrainedText)
    .Create();
        var input = _fixture.Build<HasAccountNumber>()
                            .With(x => x.AccountNumber,
                                  new SpecimenContext(new RandomStringOfLengthGenerator())
                                      .Resolve(new RandomStringOfLengthRequest(50)))
                            .Create();
var输入=_fixture.Build()
.使用(x=>x.AccountNumber,
新的SpecimenContext(新的RandomStringOfLengthGenerator())
.Resolve(新的随机长度请求(50)))
.Create();

我在项目中添加了一个自定义字符串生成器。它附加了一个4位数字,而不是guid

 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();
        }
    }

其他一些解决方案相当不错,但是如果您在基于数据模型的测试夹具中生成对象,您将遇到其他问题。首先,对于代码优先的数据模型,StringLength属性不是一个很好的选项,因为它添加了看起来重复的注释。不清楚为什么您需要StringLength和StringLengthh和MaxLength。手动保持它们同步是相当多余的

我倾向于定制夹具的工作方式

1) 您可以自定义类的fixture,并指定在创建该属性时,根据需要截断字符串。因此,要将MyClass中需要struncation的字段截断为10个字符,请使用以下命令:

fixture.Customize<MyClass>(c => c
  .With(x => x.FieldThatNeedsTruncation, Fixture.Create<string>().Substring(0,10));

注意:此解决方案并不真正使用AutoFixture,但有时使用软件包比自己编程更困难。

为什么要使用自动对焦?当使用自动对焦更难、更难看时,我首选的用法是:

var fixture = new Fixture();
fixture.Create<string>(length: 9);
var fixture=newfixture();
夹具。创建(长度: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)");
    }
}
公共静态类FixtureExtensions
{
公共静态T创建(此IFixture fixture,int-length),其中T:IConvertible、IComparable、IEquatable
{
if(typeof(T)=typeof(string))
{
//这里有一些长度缺陷,但你明白了。
var值=fixture.Create();
if(value.Length
这是我的解决方案和注意事项

首先,很明显AutoFixture中存在一些紧密耦合。创建以了解如何构建和自定义样本。对于字符串,这很烦人,因为我们知道默认值是Guid。使用
/// <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());
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)");
    }
}
public static string GetStringOfLength(this IFixture fixture, int length)
    {
        return string.Join("", fixture.CreateMany<char>(length));
    }