为什么 Matching.ImplementInterfaces 的行为与 Matching.ExactType 和 F

本文关键字:Matching ExactType ImplementInterfaces 为什么 | 更新日期: 2023-09-27 18:30:46

请考虑以下代码:

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 枚举,因此我的期望是它的行为与以前相同。

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

我确实做了一些洞穴探险,发现Match.ExactTypeFrozenAttribute.As利用SeedRequestSpecification,而Match.ImplementedInterfaces只匹配Type请求。

是否有可能获得有关此行为的一些上下文? 这是设计使然吗? 如果是这样,是否有已知的建议以这种方式设计以使用 Match.ImplementedInterfaces 恢复旧行为?

为什么 Matching.ImplementInterfaces 的行为与 Matching.ExactType 和 F

首先,附带条件: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
}

这是最好的实现吗?也许不是,但这就是它目前的工作方式。AutoFixture GitHub 存储库中存在一个未解决的问题,建议应重构冻结实现,使其更像 DI 容器的单例生存期。这可能会将这种特定情况下的行为更改为更适合它的行为。它是否会有一些缺点,我目前还不能说。

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

虽然As使你能够使这个特定的功能工作,这是因为你作为程序员知道Class实现IInterface,因此[Frozen(As = typeof(IInterface))]注释是有意义的。

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

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

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

AutoFixture最初被认为是一个测试驱动开发工具,这仍然是它的主要目的。本着全球海洋观测系统的精神,我们应该听取试验。如果测试很难写,第一反应应该是简化SUT。AutoFixture倾向于放大测试中的这种反馈。

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

可以变得更简单吗?