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

Mik*_*EEE 2 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.
    }
}
Run Code Online (Sandbox Code Playgroud)

正如您所希望的那样,ImplementedInterfaces测试失败了.由于FrozenAttribute.As已经被弃用并且用户被指示移动到Match枚举,我的期望是它将表现得与以前相同.

然而,它似乎是Match.ImplementedInterfaces从两个不同的行为Match.ExactTypeFrozenAttribute.As.

我确实做了一些洞察并看到了Match.ExactType并且FrozenAttribute.As利用了SeedRequestSpecificationMatch.ImplementedInterfaces只是匹配Type请求.

是否有可能围绕这种行为获得一些背景?这是设计的吗?如果是这样,是否有人建议以这种方式设计以恢复旧的行为Match.ImplementedInterfaces

Mar*_*ann 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
}
Run Code Online (Sandbox Code Playgroud)

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

简短的解释是,对于当前的实现,冻结是在反射时完成的,而不是在运行时完成的.当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
}
Run Code Online (Sandbox Code Playgroud)

这是最好的实施吗?也许不是,但这就是目前的工作方式.AutoFixture GitHub存储库中存在一个未解决的问题,建议冻结实现应该重构为更像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倾向于放大测试的反馈.

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

可以变得更简单吗?