为什么 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.ExactType
和FrozenAttribute.As
不同。
我确实做了一些洞穴探险,发现Match.ExactType
和FrozenAttribute.As
利用SeedRequestSpecification
,而Match.ImplementedInterfaces
只匹配Type
请求。
是否有可能获得有关此行为的一些上下文? 这是设计使然吗? 如果是这样,是否有已知的建议以这种方式设计以使用 Match.ImplementedInterfaces
恢复旧行为?
首先,附带条件: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倾向于放大测试中的这种反馈。
你真的需要匹配既实现接口又派生自基类的东西吗?为什么?
可以变得更简单吗?