AutoFixture 可以省略 class 层次结构之间的递归吗?

Can AutoFixture omit recursion between class hierarchies?

考虑以下表示家庭成员的方式:

public abstract class Human { }
public abstract class Child : Human
{
    public ICollection<Parent> Parents { get; set; }
}
public abstract class Parent : Human
{
    public ICollection<Child> Children { get; set; }
}
public class Son : Child { }
public class Daughter : Child { }
public class Mum : Parent { }
public class Dad : Parent { }

现在,我希望 AutoFixture 生成 Parent,在 MumDad 之间随机选择,而 children 在 Mum 和 children 之间随机选择SonDaughter。我还希望它省略递归,所以如果它来自 Parent 并生成 Child,它可以省略 link 回到 Parent

我尝试了以下定制:

var fixture = new Fixture();

fixture.Behaviors.OfType<ThrowingRecursionBehavior>().ToList()
    .ForEach(b => fixture.Behaviors.Remove(b));
fixture.Behaviors.Add(new OmitOnRecursionBehavior());

var random = new Random();
fixture.Register<Parent>(() =>
{
    switch (random.Next(1, 2))
    {
        case 1:
            return fixture.Create<Mum>();
        case 2:
            return fixture.Create<Dad>();
        default:
            throw new NotImplementedException();
    }
});
fixture.Register<Child>(() =>
{
    switch (random.Next(1, 2))
    {
        case 1:
            return fixture.Create<Son>();
        case 2:
            return fixture.Create<Daughter>();
        default:
            throw new NotImplementedException();
    }
});

fixture.Create<Parent>();

但它抛出一个 InvalidCastException(见下文)。

有没有办法配置 AutoFixture,使其考虑 Parent -> Child -> Parent 递归,即使它实际上为每个实例随机选择一个合适的子类?

Unhandled Exception: AutoFixture.ObjectCreationExceptionWithPath: AutoFixture was unable to
create an instance from AutoFixtureAbstractTrees.Parent because creation unexpectedly
failed with exception. Please refer to the inner exception to investigate the root cause of
the failure.

Request path:
        AutoFixtureAbstractTrees.Mum
          System.Collections.Generic.ICollection`1[AutoFixtureAbstractTrees.Child] Children
            System.Collections.Generic.ICollection`1[AutoFixtureAbstractTrees.Child]
              System.Collections.Generic.List`1[AutoFixtureAbstractTrees.Child]
                System.Collections.Generic.IEnumerable`1[AutoFixtureAbstractTrees.Child] collection
                  System.Collections.Generic.IEnumerable`1[AutoFixtureAbstractTrees.Child]
                    AutoFixtureAbstractTrees.Child
                      AutoFixtureAbstractTrees.Son
                        System.Collections.Generic.ICollection`1[AutoFixtureAbstractTrees.Parent] Parents
                          System.Collections.Generic.ICollection`1[AutoFixtureAbstractTrees.Parent]
                            System.Collections.Generic.List`1[AutoFixtureAbstractTrees.Parent]
                              System.Collections.Generic.IEnumerable`1[AutoFixtureAbstractTrees.Parent] collection
                                System.Collections.Generic.IEnumerable`1[AutoFixtureAbstractTrees.Parent]
                                  AutoFixtureAbstractTrees.Parent

Inner exception messages:
        System.InvalidCastException: Unable to cast object of type
        'AutoFixture.Kernel.OmitSpecimen' to type 'AutoFixtureAbstractTrees.Mum'.

您遇到此问题的原因是 AutoFixture 中的设计缺陷。当您使用 Create 扩展方法时,您实际上启动了一个新的解析上下文,而递归保护机制不会捕捉到它。

看起来,在这种情况下,您可以通过使用 context 中的 ISpecimenBuilders 和 Resolve 而不是使用 Create 扩展方法来解决该问题:

[Fact]
public void WorkAround()
{
    var fixture = new Fixture();

    fixture.Behaviors.OfType<ThrowingRecursionBehavior>().ToList()
        .ForEach(b => fixture.Behaviors.Remove(b));
    fixture.Behaviors.Add(new OmitOnRecursionBehavior(3));

    var random = new Random();
    fixture.Customizations.Add(new ParentBuilder(random));
    fixture.Customizations.Add(new ChildBuilder(random));

    var actual = fixture.Create<Parent>();

    Assert.True(0 < actual.Children.Count);
}

此测试通过,并使用自定义 类 ParentBuilderChildBuilder:

public class ParentBuilder : ISpecimenBuilder
{
    private readonly Random random;

    public ParentBuilder(Random random)
    {
        this.random = random;
    }

    public object Create(object request, ISpecimenContext context)
    {
        var t = request as Type;
        if (t == null || t != typeof(Parent))
            return new NoSpecimen();

        if (this.random.Next(0, 2) == 0)
            return context.Resolve(typeof(Mum));
        else
            return context.Resolve(typeof(Dad));
    }
}

public class ChildBuilder : ISpecimenBuilder
{
    private readonly Random random;

    public ChildBuilder(Random random)
    {
        this.random = random;
    }

    public object Create(object request, ISpecimenContext context)
    {
        var t = request as Type;
        if (t == null || t != typeof(Child))
            return new NoSpecimen();

        if (this.random.Next(0, 2) == 0)
            return context.Resolve(typeof(Son));
        else
            return context.Resolve(typeof(Daughter));
    }
}

综上所述,作为 ,您正在突破 AutoFixture 的极限。它并不是真正为处理复杂的递归对象设计而设计的,就像这里显示的那样。