12

我正在尝试使用 Autofixture 控制对象树的生成深度。在某些情况下,我只想生成根对象,而在另一组情况下,我可能想生成一定深度的树(例如,2、3)。

class Foo {
    public string Name {get;set;}
    public Bar Bar {get;set;}
    public AnotherType Xpto {get;set;}
    public YetAnotherType Xpto {get;set;}
}

class Bar {
    public string Name {get;set;}
    public string Description {get;set;}
    public AnotherType Xpto {get;set;}
    public YetAnotherType Xpto {get;set;}
    public Xpto Xpto {get;set;}
}

class Xpto {
    public string Description {get;set;}
    public AnotherType Xpto {get;set;}
    public YetAnotherType Xpto {get;set;}
}

对于上面的示例,我希望(深度 1)控制生成过程,以便仅实例化 Foo 类,并且不填充该类上的 Bar 属性或任何其他引用类型,或者(深度 2)我想要 Foo 类实例化后,使用 Bar 的新实例填充 Bar 属性,但未填充 Xpto 属性或该类上的任何其他引用类型

如果我没有在代码库中发现它,Autofixture 是否有自定义或行为允许我们进行这种控制?

同样,我想要控制的不是递归,而是对象图的填充深度。

4

3 回答 3

6

没有酒吧

一次性:

var f = fixture.Build<Foo>().Without(f => f.Bar).Create();

可重复使用的:

fixture.Customize<Foo>(c => c.Without(f => f.Bar));
var f = fixture.Create<Foo>();

没有 Xpto

一次性:

var f = fixture
    .Build<Foo>()
    .With(
        f => f.Bar,
        fixture.Build<Bar>().Without(b => b.Xpto).Create())
    .Create();

可重复使用的:

fixture.Customize<Bar>(c => c.Without(b => b.Xpto));
var f = fixture.Create<Foo>();
于 2013-11-13T15:18:41.897 回答
6

您可以按如下方式使用以下GenerationDepthBehavior类:

fixture.Behaviors.Add(new GenerationDepthBehavior(2));

public class GenerationDepthBehavior : ISpecimenBuilderTransformation
{
    private const int DefaultGenerationDepth = 1;
    private readonly int generationDepth;

    public GenerationDepthBehavior() : this(DefaultGenerationDepth)
    {
    }

    public GenerationDepthBehavior(int generationDepth)
    {
        if (generationDepth < 1)
            throw new ArgumentOutOfRangeException(nameof(generationDepth), "Generation depth must be greater than 0.");

        this.generationDepth = generationDepth;
    }

    public ISpecimenBuilderNode Transform(ISpecimenBuilder builder)
    {
        if (builder == null) throw new ArgumentNullException(nameof(builder));

        return new GenerationDepthGuard(builder, new GenerationDepthHandler(), this.generationDepth);
    }
}

public interface IGenerationDepthHandler
{
    object HandleGenerationDepthLimitRequest(object request, IEnumerable<object> recordedRequests, int depth);
}

public class DepthSeededRequest : SeededRequest
{
    public int Depth { get; }

    public int MaxDepth { get; set; }

    public bool ContinueSeed { get; }

    public int GenerationLevel { get; private set; }

    public DepthSeededRequest(object request, object seed, int depth) : base(request, seed)
    {
        Depth = depth;

        Type innerRequest = request as Type;

        if (innerRequest != null)
        {
            bool nullable = Nullable.GetUnderlyingType(innerRequest) != null;

            ContinueSeed = nullable || innerRequest.IsGenericType;

            if (ContinueSeed)
            {
                GenerationLevel = GetGenerationLevel(innerRequest);
            }
        }
    }

    private int GetGenerationLevel(Type innerRequest)
    {
        int level = 0;

        if (Nullable.GetUnderlyingType(innerRequest) != null)
        {
            level = 1;
        }

        if (innerRequest.IsGenericType)
        {
            foreach (Type generic in innerRequest.GetGenericArguments())
            {
                level++;

                level += GetGenerationLevel(generic);
            }
        }

        return level;
    }
}

public class GenerationDepthGuard : ISpecimenBuilderNode
{
    private readonly ThreadLocal<Stack<DepthSeededRequest>> requestsByThread
        = new ThreadLocal<Stack<DepthSeededRequest>>(() => new Stack<DepthSeededRequest>());

    private Stack<DepthSeededRequest> GetMonitoredRequestsForCurrentThread() => this.requestsByThread.Value;

    public GenerationDepthGuard(ISpecimenBuilder builder)
        : this(builder, EqualityComparer<object>.Default)
    {
    }

    public GenerationDepthGuard(
        ISpecimenBuilder builder,
        IGenerationDepthHandler depthHandler)
        : this(
            builder,
            depthHandler,
            EqualityComparer<object>.Default,
            1)
    {
    }

    public GenerationDepthGuard(
        ISpecimenBuilder builder,
        IGenerationDepthHandler depthHandler,
        int generationDepth)
        : this(
            builder,
            depthHandler,
            EqualityComparer<object>.Default,
            generationDepth)
    {
    }

    public GenerationDepthGuard(ISpecimenBuilder builder, IEqualityComparer comparer)
    {
        this.Builder = builder ?? throw new ArgumentNullException(nameof(builder));
        this.Comparer = comparer ?? throw new ArgumentNullException(nameof(comparer));
        this.GenerationDepth = 1;
    }

    public GenerationDepthGuard(
        ISpecimenBuilder builder,
        IGenerationDepthHandler depthHandler,
        IEqualityComparer comparer)
        : this(
        builder,
        depthHandler,
        comparer,
        1)
    {
    }

    public GenerationDepthGuard(
        ISpecimenBuilder builder,
        IGenerationDepthHandler depthHandler,
        IEqualityComparer comparer,
        int generationDepth)
    {
        if (builder == null) throw new ArgumentNullException(nameof(builder));
        if (depthHandler == null) throw new ArgumentNullException(nameof(depthHandler));
        if (comparer == null) throw new ArgumentNullException(nameof(comparer));
        if (generationDepth < 1)
            throw new ArgumentOutOfRangeException(nameof(generationDepth), "Generation depth must be greater than 0.");

        this.Builder = builder;
        this.GenerationDepthHandler = depthHandler;
        this.Comparer = comparer;
        this.GenerationDepth = generationDepth;
    }

    public ISpecimenBuilder Builder { get; }

    public IGenerationDepthHandler GenerationDepthHandler { get; }

    public int GenerationDepth { get; }

    public int CurrentDepth { get; }

    public IEqualityComparer Comparer { get; }

    protected IEnumerable RecordedRequests => this.GetMonitoredRequestsForCurrentThread();

    public virtual object HandleGenerationDepthLimitRequest(object request, int currentDepth)
    {
        return this.GenerationDepthHandler.HandleGenerationDepthLimitRequest(
            request,
            this.GetMonitoredRequestsForCurrentThread(), currentDepth);
    }

    public object Create(object request, ISpecimenContext context)
    {
        if (request is SeededRequest)
        {
            int currentDepth = 0;

            var requestsForCurrentThread = GetMonitoredRequestsForCurrentThread();

            if (requestsForCurrentThread.Count > 0)
            {
                currentDepth = requestsForCurrentThread.Max(x => x.Depth) + 1;
            }

            DepthSeededRequest depthRequest = new DepthSeededRequest(((SeededRequest)request).Request, ((SeededRequest)request).Seed, currentDepth);

            if (depthRequest.Depth >= GenerationDepth)
            {
                var parentRequest = requestsForCurrentThread.Peek();

                depthRequest.MaxDepth = parentRequest.Depth + parentRequest.GenerationLevel;

                if (!(parentRequest.ContinueSeed && currentDepth < depthRequest.MaxDepth))
                {
                    return HandleGenerationDepthLimitRequest(request, depthRequest.Depth);
                }
            }

            requestsForCurrentThread.Push(depthRequest);
            try
            {
                return Builder.Create(request, context);
            }
            finally
            {
                requestsForCurrentThread.Pop();
            }
        }
        else
        {
            return Builder.Create(request, context);
        }
    }

    public virtual ISpecimenBuilderNode Compose(
        IEnumerable<ISpecimenBuilder> builders)
    {
        var composedBuilder = ComposeIfMultiple(
            builders);
        return new GenerationDepthGuard(
            composedBuilder,
            this.GenerationDepthHandler,
            this.Comparer,
            this.GenerationDepth);
    }

    internal static ISpecimenBuilder ComposeIfMultiple(IEnumerable<ISpecimenBuilder> builders)
    {
        ISpecimenBuilder singleItem = null;
        List<ISpecimenBuilder> multipleItems = null;
        bool hasItems = false;

        using (var enumerator = builders.GetEnumerator())
        {
            if (enumerator.MoveNext())
            {
                singleItem = enumerator.Current;
                hasItems = true;

                while (enumerator.MoveNext())
                {
                    if (multipleItems == null)
                    {
                        multipleItems = new List<ISpecimenBuilder> { singleItem };
                    }

                    multipleItems.Add(enumerator.Current);
                }
            }
        }

        if (!hasItems)
        {
            return new CompositeSpecimenBuilder();
        }

        if (multipleItems == null)
        {
            return singleItem;
        }

        return new CompositeSpecimenBuilder(multipleItems);
    }

    public virtual IEnumerator<ISpecimenBuilder> GetEnumerator()
    {
        yield return this.Builder;
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

public class GenerationDepthHandler : IGenerationDepthHandler
{
    public object HandleGenerationDepthLimitRequest(
        object request,
        IEnumerable<object> recordedRequests, int depth)
    {
        return new OmitSpecimen();
    }
}
于 2018-05-01T15:12:14.533 回答
1

此功能是在github 问题中请求的。它最终被拒绝了。但是,它被拒绝了,因为在该问题中发布了一个不错的简单解决方案。

public class GenerationDepthBehavior: ISpecimenBuilderTransformation
{
    public int Depth { get; }

    public GenerationDepthBehavior(int depth)
    {
        Depth = depth;
    }

    public ISpecimenBuilderNode Transform(ISpecimenBuilder builder)
    {
        return new RecursionGuard(builder, new OmitOnRecursionHandler(), new IsSeededRequestComparer(), Depth);
    }

    private class IsSeededRequestComparer : IEqualityComparer
    {
        bool IEqualityComparer.Equals(object x, object y)
        {
            return x is SeededRequest && y is SeededRequest;
        }

        int IEqualityComparer.GetHashCode(object obj)
        {
            return obj is SeededRequest ? 0 : EqualityComparer<object>.Default.GetHashCode(obj);
        }
    }
}

然后,您可以按如下方式使用它:

fixture.Behaviors.Add(new GenerationDepthBehavior(2));

于 2019-08-08T17:49:56.963 回答