假设SettingKey
属性定义如下,我现在可以重现该问题:
public string SettingKey { get; set; }
发生的情况是测试双打 http://xunitpatterns.com/Test%20Double.html注入到SettingMappingXml实例中完全没问题,但是因为SettingKey
是可写的,AutoFixture 的自动属性功能会启动并修改该值。
考虑这段代码:
var fixture = new Fixture().Customize(new AutoMoqCustomization());
var sut = fixture.CreateAnonymous<SettingMappingXml>();
Console.WriteLine(sut.SettingKey);
这会打印出类似这样的内容:
设置Key83b75965-2886-4308-bcc4-eb0f8e63de09
即使所有测试替身都已正确注入,但预期Setup
方法不满足。
有很多方法可以解决这个问题。
保护不变量
解决这个问题的正确方法是使用单元测试和AutoFixture作为反馈机制。这是其中的关键点之一GOOS http://amzn.to/SM8Yv0:单元测试的问题通常是设计缺陷的症状,而不是单元测试(或 AutoFixture)本身的错误。
在这种情况下,它向我表明设计不够万无一失 http://blog.ploeh.dk/2011/05/26/CodeSmellAutomaticProperty.aspx。客户可以操纵真的合适吗?SettingKey
随意?
至少,我会推荐这样的替代实现:
public string SettingKey { get; private set; }
有了这个改变,我的重现就过去了。
省略设置键
如果您不能(或不会)更改您的设计,您可以指示 AutoFixture 跳过设置SettingKey
财产:
IMappingXml sut = fixture
.Build<SettingMappingXml>()
.Without(s => s.SettingKey)
.CreateAnonymous();
就我个人而言,我发现必须写一个适得其反的Build
每次我需要特定类的实例时都会表达。您可以解耦如何SettingMappingXml
实例是从实际实例化创建的:
fixture.Customize<SettingMappingXml>(
c => c.Without(s => s.SettingKey));
IMappingXml sut = fixture.CreateAnonymous<SettingMappingXml>();
为了更进一步,您可以封装它Customize
方法调用定制 http://blog.ploeh.dk/2011/03/18/EncapsulatingAutoFixtureCustomizations.aspx.
public class SettingMappingXmlCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customize<SettingMappingXml>(
c => c.Without(s => s.SettingKey));
}
}
这需要您创建您的Fixture
具有该自定义的实例:
IFixture fixture = new Fixture()
.Customize(new SettingMappingXmlCustomization())
.Customize(new AutoMoqCustomization());
一旦您获得了两个或三个以上的自定义链接,您可能会厌倦一直编写该方法链。是时候将这些自定义封装到您的特定库的一组约定中了:
public class TestConventions : CompositeCustomization
{
public TestConventions()
: base(
new SettingMappingXmlCustomization(),
new AutoMoqCustomization())
{
}
}
这使您能够始终创建Fixture
像这样的实例:
IFixture fixture = new Fixture().Customize(new TestConventions());
The TestConventions
为您提供了一个中心位置,您可以在需要时偶尔修改测试套件的约定。它减少了单元测试的可维护性负担,并有助于保持生产代码的设计更加一致。
最后,由于看起来您正在使用 xUnit.net,因此您可以利用AutoFixture 的 xUnit.net 集成 http://blog.ploeh.dk/2010/10/08/AutoDataTheoriesWithAutoFixture.aspx,但在执行此操作之前,您需要使用一种不太强制的方式来操作Fixture
。事实证明,创建、配置和注入的代码ISettings
Test Double 非常惯用,它有一个快捷方式称为Freeze http://blog.ploeh.dk/2010/03/17/AutoFixtureFreeze.aspx:
fixture.Freeze<Mock<ISettings>>()
.Setup(s => s.Get(settingKey)).Returns(xmlString);
完成后,下一步是定义自定义 AutoDataAttribute:
public class AutoConventionDataAttribute : AutoDataAttribute
{
public AutoConventionDataAttribute()
: base(new Fixture().Customize(new TestConventions()))
{
}
}
现在,您可以将测试简化为最基本的内容,消除所有噪音,使测试能够简洁地表达重要的内容:
[Theory, AutoConventionData]
public void ReducedTheory(
[Frozen]Mock<ISettings> settingsStub,
SettingMappingXml sut)
{
string xmlString = @"
<mappings>
<mapping source='gcnm_loan_amount_min' target='gcnm_loan_amount_min_usd' />
<mapping source='gcnm_loan_amount_max' target='gcnm_loan_amount_max_usd' />
</mappings>";
string settingKey = "gcCreditApplicationUsdFieldMappings";
settingsStub.Setup(s => s.Get(settingKey)).Returns(xmlString);
XElement actualXml = sut.GetXml();
XElement expectedXml = XElement.Parse(xmlString);
Assert.True(XNode.DeepEquals(expectedXml, actualXml));
}
其他选项
要使原始测试通过,您也可以完全关闭自动属性:
fixture.OmitAutoProperties = true;