Подтвердить что ты не робот

Как использовать Autofixture (v3) с помощью функции ICustomization, ISpecimenBuilder для работы с параметром конструктора?

Я пытаюсь преодолеть сценарий, в котором класс имеет параметр конструктора строк, который не может быть удовлетворен какой-либо старой строкой, сгенерированной Autofixture (ориентировочное значение Guid-y).

Прежде чем соблазняться ответить просто ссылкой на запись блога Mark Seemann Ploeh на основе настроек на основе Конвенции, позвольте мне сказать, что я ссылался на нее и другие записи в блоге его для этого теста, которые я не могу пройти.

Когда я перехожу к отладке, я вижу, что в какой-то момент параметр конструктора передается с допустимым значением, но тест по-прежнему не выполняется с помощью значения Guid-y Color. Я думаю, что это имеет какое-то отношение к тому факту, что есть значение параметра "цвет" и свойство "Цвет", которое будет заполнено Autofixture. Разве это то, что я написал ISpecimenBuilder, который обращается к параметру конструктора, но я проверяю значение публичной собственности (две разные вещи)?

Я знаю, что все это слишком много для примера, но я предполагаю более сложный сценарий, в котором использование метода Build<T>().With() не было бы СУХОЙ.

Неудачный тест

    [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()) { }
    }

Специализация, специфичная для Leaf

    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-specific 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";
        }
    }
4b9b3361

Ответ 1

Решение 1:

Зарегистрируйте, что свойство Color writeable не должно иметь никакого автоматического значения как часть последующей обработки:

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.

Ответ 2

Предполагая, что вы имеете дело с настройками свойств отдельно, здесь ограничение аргумента конструктора 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();
            }
        }
    }
}

Ответ 3

Решение (на основе Mark Seemann комментарий к этому ответу)

Устанавливайте как параметр конструктора, так и свойство записи в реализации ISpecimenBuilder и не делайте ничего, кроме добавления экземпляра LeafBuilder в LeafColorCustomization:

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