问题 如何使用自动混合(v3)与ICustomization,ISpecimenBuilder来处理构造函数参数?


我正在尝试克服一个类具有字符串构造函数参数的情况,该参数不能被Autofixture生成的任何旧字符串(Guid-y外观值)所满足。

在您想要简单地回答链接之前 Mark Seemann的关于基于会议的定制的Ploeh博客文章,让我说,我一直在引用它和他的其他博客条目进行此测试,我无法通过。

当我在调试中单步执行时,我可以看到构造函数参数在某些时候传入了有效值,但测试仍然失败并带有Guid-y Color值。我认为这与存在“颜色”参数值这一事实有关,  由Autofixture填充的“颜色”属性。是不是我编写了一个解决构造函数参数的ISpecimenBuilder,但我正在测试公共属性值(两个不同的东西)?

我知道所有这些对于这个例子来说都是过度的,但我想到了一个更复杂的场景,其中使用了 Build<T>().With() 方法不会是DRY。

失败测试

    [Fact]
    public void Leaf_Color_Is_Brown()
    {
        // arrange
        var fixture = new Fixture().Customize(new LeafColorCustomization());

        // act
        var leaf = fixture.Create<Leaf>();

        // using .Build<>.With(), test passes
        //var leaf = fixture.Build<Leaf>().With(l => l.Color, "brown").CreateAnonymous();

        // assert
        Assert.True(leaf.Color == "brown");
    }

SUT

    public class Leaf
    {
        public Leaf(string color)
        {
            if (color != "brown")
                throw new ArgumentException(@"NO LEAF FOR YOU!");

            this.Color = color;
        }
        public string Color { get; set; }
    }

CompositeCustomization实现 (我知道在这个例子中不需要AutoMoqCustomization())

    public class LeafCustomization : CompositeCustomization
    {
        public LeafCustomization()
            : base(
            new LeafColorCustomization(),
            new AutoMoqCustomization()) { }
    }

叶特定的ICustomization

    public class LeafColorCustomization : ICustomization
    {
        public void Customize(IFixture fixture)
        {
            if (fixture == null)
                throw new ArgumentNullException("fixture");

            fixture.Customizations.Add(new LeafBuilder());
        }
    }

String-constructor-with-name-of-Color特定的ISpecimenBuilder

    public class LeafBuilder : ISpecimenBuilder
    {
        public object Create(object request, ISpecimenContext context)
        {
            var pi = request as ParameterInfo;
            if (pi == null)
                return new NoSpecimen(request);

            if (pi.ParameterType != typeof(string) || pi.Name != "color")
                return new NoSpecimen(request);

            return "brown";
        }
    }

12069
2018-03-20 18:00


起源



答案:


解决方案1

注册那个 Color 作为后处理的一部分,不应为可写属性分配任何自动值:

internal class LeafColorCustomization : ICustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Customize<Leaf>(c => c
            .Without(x => x.Color));

        fixture.Customizations.Add(new LeafBuilder());
    }
}

解决方案2

制作 Color 财产只读:

public class Leaf
{
    private readonly string color;

    public Leaf(string color)
    {
        if (color != "brown")
            throw new ArgumentException(@"NO LEAF FOR YOU!");

        this.color = color;
    }

    public string Color
    {
        get { return this.color; }
    }
}

自从 Color 属性是只读的AutoFixture不会为它分配值。

以上解决方案也适用于AutoFixture 2。


7
2018-03-20 21:06



辉煌 - 有效! Re:解决方案#1,可以/应该指定跳过 Color 里面的可写属性 ISpecimenBuilder (LeafBuilder而不是在里面 ICustomization (LeafColorCustomization)?我的印象是其中很多 .Without() 警告会污染实施,并且除了一系列之外什么都不会更清洁(特别是在更复杂的例子中) fixture.Customizations.Add(new XXXBuilder()); 在 - 的里面 ICustomization  - 我不明白不同的自定义属于哪里? - Jeff
@Lumirris你绝对可以从内部处理这个问题 LeafBuilder。你只需要处理这种情况 request 是一个 PropertyInfo 除了你已经处理过的情况 ParameterInfo。 - Mark Seemann
@MarkSeemann - YAGNI 尽管有问题,最好是从内部处理 ISpecimenBuilder 履行 LeafBuilder 在考虑更复杂的场景时,使用 ICustomization 实现只是添加所有必要的 ISpecimenBuilder 实现? - Jeff
在这种特殊情况下,我肯定会使用 ISpecimenBuilder 喜欢 LeafBuilder 因为它使您能够实现细粒度,基于约定的启发式方法。 OTOH,它比使用暴露的强类型方法更有效 IFixture所以如果我能以这种方式表达我想要的东西,我会使用它们。 - Mark Seemann


答案:


解决方案1

注册那个 Color 作为后处理的一部分,不应为可写属性分配任何自动值:

internal class LeafColorCustomization : ICustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Customize<Leaf>(c => c
            .Without(x => x.Color));

        fixture.Customizations.Add(new LeafBuilder());
    }
}

解决方案2

制作 Color 财产只读:

public class Leaf
{
    private readonly string color;

    public Leaf(string color)
    {
        if (color != "brown")
            throw new ArgumentException(@"NO LEAF FOR YOU!");

        this.color = color;
    }

    public string Color
    {
        get { return this.color; }
    }
}

自从 Color 属性是只读的AutoFixture不会为它分配值。

以上解决方案也适用于AutoFixture 2。


7
2018-03-20 21:06



辉煌 - 有效! Re:解决方案#1,可以/应该指定跳过 Color 里面的可写属性 ISpecimenBuilder (LeafBuilder而不是在里面 ICustomization (LeafColorCustomization)?我的印象是其中很多 .Without() 警告会污染实施,并且除了一系列之外什么都不会更清洁(特别是在更复杂的例子中) fixture.Customizations.Add(new XXXBuilder()); 在 - 的里面 ICustomization  - 我不明白不同的自定义属于哪里? - Jeff
@Lumirris你绝对可以从内部处理这个问题 LeafBuilder。你只需要处理这种情况 request 是一个 PropertyInfo 除了你已经处理过的情况 ParameterInfo。 - Mark Seemann
@MarkSeemann - YAGNI 尽管有问题,最好是从内部处理 ISpecimenBuilder 履行 LeafBuilder 在考虑更复杂的场景时,使用 ICustomization 实现只是添加所有必要的 ISpecimenBuilder 实现? - Jeff
在这种特殊情况下,我肯定会使用 ISpecimenBuilder 喜欢 LeafBuilder 因为它使您能够实现细粒度,基于约定的启发式方法。 OTOH,它比使用暴露的强类型方法更有效 IFixture所以如果我能以这种方式表达我想要的东西,我会使用它们。 - Mark Seemann


假设你单独处理属性设置的东西,这里是一个构造函数参数限制 Customization 这样做的诀窍:

class BrownLeavesCustomization : ICustomization
{
    void ICustomization.Customize( IFixture fixture )
    {
        Func<string> notBrownGenerator = fixture.Create<Generator<string>>()
            .SkipWhile( x => x == "Brown" )
            .First;
        fixture.Customizations.Add( 
            ArgumentGeneratorCustomization<Leaf>.ForConstructorArgument(
                "color", 
                notBrownGenerator ) );
    }

    static class ArgumentGeneratorCustomization<T>
    {
        public static ISpecimenBuilder ForConstructorArgument<TArg>( string argumentName, Func<TArg> generator )
        {
            return new ConstructorArgumentGenerator<TArg>( argumentName, generator );
        }

        class ConstructorArgumentGenerator<TArg> : ISpecimenBuilder
        {
            readonly string _argumentName;
            readonly Func<TArg> _generator;

            public ConstructorArgumentGenerator( string argumentName, Func<TArg> generator )
            {
                Assert.Contains( argumentName, from ctor in typeof( T ).GetConstructors() from param in ctor.GetParameters() select param.Name );
                _argumentName = argumentName;
                _generator = generator;
            }

            object ISpecimenBuilder.Create( object request, ISpecimenContext context )
            {
                var pi = request as ParameterInfo;
                if ( pi == null )
                    return new NoSpecimen( request );
                if ( pi.Member.DeclaringType != typeof( T ) )
                    return new NoSpecimen( request );
                if ( pi.Member.MemberType != MemberTypes.Constructor )
                    return new NoSpecimen( request );
                if ( pi.ParameterType != typeof( TArg ) )
                    return new NoSpecimen( request );
                if ( pi.Name != _argumentName )
                    return new NoSpecimen( request );

                return _generator();
            }
        }
    }
}

5
2018-03-21 14:37



@NikosBaxevanis我在这里的真正原因是看它是否能引起你的反应等:)了解AF本身或公开可用的任何类似结构?有什么改进的想法吗? - Ruben Bartelink


解决方案:(基于Mark Seemann的 评论这个答案

在ISpecimenBuilder实现中容纳构造函数参数和可写属性,除了在LeafColorCustomization中添加LeafBuilder实例之外什么都不做:

public class LeafBuilder : ISpecimenBuilder
{
    public object Create(object request, ISpecimenContext context)
    {
        var paramInfo = request as ParameterInfo;
        if (paramInfo != null
            && paramInfo.ParameterType == typeof(string)
            && paramInfo.Name == "color")
        { return "brown"; }

        var propInfo = request as PropertyInfo;
        if (propInfo != null
            && propInfo.PropertyType == typeof(string)
            && propInfo.Name == "Color")
        { return "brown"; }

        return new NoSpecimen(request);
    }
}

internal class LeafColorCustomization : ICustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Customizations.Add(new LeafBuilder());
    }
}

1
2018-03-22 02:32