请考虑以下代码:
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.ExactType
和FrozenAttribute.As
的行为不同。
我确实做了一些洞察,看到Match.ExactType
和FrozenAttribute.As
使用了SeedRequestSpecification
而Match.ImplementedInterfaces
仅匹配Type
次请求。
是否可以针对此行为获取一些背景信息?这是设计的吗?如果是这样,是否有人建议以这种方式设计以使用Match.ImplementedInterfaces
恢复旧行为?
答案 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倾向于从测试中放大这种反馈。
你真的需要匹配既实现接口又来自基类的东西?为什么呢?
它会变得更简单吗?