为什么Matching.ImplementedInterfaces的行为与Matching.ExactType和FrozenAttribute.As?

时间:2016-01-17 15:22:40

标签: c# autofixture

请考虑以下代码:

public class TestingSample
{
    public class FactoryClass : Class {}

    public class Class : IInterface {}

    public interface IInterface {}

    public class AutoData : AutoDataAttribute
    {
        public AutoData() : base( Create() ) {}

        static IFixture Create()
        {
            var fixture = new Fixture();
            fixture.Customize<IInterface>( composer => composer.FromFactory( () => new FactoryClass() ) );
            fixture.Customize<Class>( composer => composer.FromFactory( () => new FactoryClass() ) );
            return fixture;
        }
    }

    [Theory, TestingSample.AutoData]
    public void OldSkool( [Frozen( As = typeof(IInterface) )]Class first, Class second, IInterface third )
    {
        Assert.IsType<FactoryClass>( first );
        Assert.Same( first, second );
        Assert.Same( first, third );
    }

    [Theory, TestingSample.AutoData]
    public void DirectBaseType( [Frozen( Matching.ExactType )]Class first, Class second )
    {
        Assert.IsType<FactoryClass>( first );
        Assert.Same( first, second );
    }

    [Theory, TestingSample.AutoData]
    public void ImplementedInterfaces( [Frozen( Matching.ImplementedInterfaces )]Class first, IInterface second )
    {
        Assert.IsType<FactoryClass>( first );
        Assert.Same( first, second ); // The Fails.
    }
}

正如您所希望的那样,ImplementedInterfaces测试失败了。由于FrozenAttribute.As已被弃用,并且用户已被定向移至匹配枚举,我的期望是它的行为与以前相同。

但是,Match.ImplementedInterfaces的行为似乎与Match.ExactTypeFrozenAttribute.As的行为不同。

我确实做了一些洞察,看到Match.ExactTypeFrozenAttribute.As使用了SeedRequestSpecificationMatch.ImplementedInterfaces仅匹配Type次请求。

是否可以针对此行为获取一些背景信息?这是设计的吗?如果是这样,是否有人建议以这种方式设计以使用Match.ImplementedInterfaces恢复旧行为?

1 个答案:

答案 0 :(得分:5)

首先,一个附带条件:OP中提供的代码在我的机器上使用AutoFixture 3.39.0所描述的完全。区别在于此测试中的第一个断言通过:

[Theory, TestingSample.AutoData]
public void ImplementedInterfaces(
    [Frozen(Matching.ImplementedInterfaces)]Class first,
    IInterface second)
{
    Assert.IsType<FactoryClass>(first); // passes
    Assert.Same(first, second); // fails
}

尽管如此,我还是认为第二个断言失败了(有点)令人惊讶。

简短的解释是,对于当前的实现,冻结是在反射时完成的,而不是在运行时完成的。当 AutoFixture.Xunit2 确定要冻结的内容时,它会查看应用[Frozen]属性的参数类型。这是Class,而不是FactoryClass,因此结果是FactoryClass根本没有被冻结!

您可以从此测试中看到:

[Theory, TestingSample.AutoData]
public void FactoryClassIsNotFrozen(
    [Frozen(Matching.ImplementedInterfaces)]Class first,
    FactoryClass second)
{
    Assert.IsType<FactoryClass>(first); // passes
    Assert.IsType<FactoryClass>(second); // passes
    Assert.Same(first, second); // fails
}

这是最好的实施吗?也许不是,但这就是目前的工作方式。有an open issue in the AutoFixture GitHub repository建议冻结实现应该重构为更像DI Container的Singleton生命周期。这可能会将此特定方案中的行为更改为更适合的方式。它是否也有一些缺点,我现在还说不出来。

当我们重新设计[Frozen]属性以使用更灵活的Matching规则时,我意识到新系统无法100%替换旧As属性。我仍然认为权衡取舍是值得的。

虽然As使您可以使此特定功能正常工作,但这是因为您作为程序员知道 Class实现了IInterface,因此[Frozen(As = typeof(IInterface))]注释是有意义的。

你可以说As更灵活,但主要是因为它没有内置智能。您也可以编写[Frozen(As = typeof(IAsyncResult))]并且编译得很好 - 只是在运行时失败,因为它完全是胡说八道。

  

是否有一个已知的建议,以这种方式设计使用Match.ImplementedInterfaces恢复旧行为?

是的,请考虑简化被测系统(SUT)的设计。

AutoFixture最初被设想为测试驱动开发工具,这仍然是它的主要目的。本着GOOS的精神,我们应该听取测试。如果测试难以编写,第一反应应该是简化SUT。 AutoFixture倾向于从测试中放大这种反馈。

你真的需要匹配既实现接口又来自基类的东西?为什么呢?

它会变得更简单吗?