Tea*_*Dev 11 c# unit-testing autofixture automocking
现在找到解决方案的简短内容:
AutoFixture返回冻结模拟就好了; 我的sut也是由AutoFixture生成的,只有一个公共属性,其本地默认值对于测试非常重要,并且AutoFixture设置为新值.除了Mark的答案之外,还有很多值得学习的东西.
原始问题:
我昨天开始尝试使用AutoFixture进行我的xUnit.net测试,这些测试中包含了Moq.我希望更换一些Moq的东西或者让它更容易阅读,我特别感兴趣的是在SUT工厂容量中使用AutoFixture.
我使用Mark Seemann的一些关于AutoMocking的博客文章,并尝试从那里开始工作,但我没有走得太远.
这是我的测试看起来没有AutoFixture:
[Fact]
public void GetXml_ReturnsCorrectXElement()
{
// Arrange
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";
Mock<ISettings> settingsMock = new Mock<ISettings>();
settingsMock.Setup(s => s.Get(settingKey)).Returns(xmlString);
ISettings settings = settingsMock.Object;
ITracingService tracing = new Mock<ITracingService>().Object;
XElement expectedXml = XElement.Parse(xmlString);
IMappingXml sut = new SettingMappingXml(settings, tracing);
// Act
XElement actualXml = sut.GetXml();
// Assert
Assert.True(XNode.DeepEquals(expectedXml, actualXml));
}
Run Code Online (Sandbox Code Playgroud)
这里的故事很简单 - 确保使用正确的密钥(这是硬编码/属性注入)SettingMappingXml查询ISettings依赖关系并将结果返回为XElement.在ITracingService只有当有一个错误是相关的.
我试图做的是摆脱显式创建ITracingService对象的需要,然后手动注入依赖项(不是因为这个测试太复杂,而是因为它很简单,可以尝试并理解它们).
输入AutoFixture - 首次尝试:
[Fact]
public void GetXml_ReturnsCorrectXElement()
{
// Arrange
IFixture fixture = new Fixture();
fixture.Customize(new AutoMoqCustomization());
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";
Mock<ISettings> settingsMock = new Mock<ISettings>();
settingsMock.Setup(s => s.Get(settingKey)).Returns(xmlString);
ISettings settings = settingsMock.Object;
fixture.Inject(settings);
XElement expectedXml = XElement.Parse(xmlString);
IMappingXml sut = fixture.CreateAnonymous<SettingMappingXml>();
// Act
XElement actualXml = sut.GetXml();
// Assert
Assert.True(XNode.DeepEquals(expectedXml, actualXml));
}
Run Code Online (Sandbox Code Playgroud)
CreateAnonymous<SettingMappingXml>()在检测到ISettings构造函数参数时,我希望注意到已经为该接口注册了一个具体实例并注入了它 - 但是,它并没有这样做,而是创建了一个新的匿名实现.
这尤其令人困惑,因为fixture.CreateAnonymous<ISettings>()确实会返回我的实例 -
IMappingXml sut = new SettingMappingXml(fixture.CreateAnonymous<ISettings>(), fixture.CreateAnonymous<ITracingService>());
Run Code Online (Sandbox Code Playgroud)
使测试完全变为绿色,这一行正是我所期望的AutoFixture在实例化时内部做的SettingMappingXml.
然后是冻结组件的概念,所以我继续只是冻结了夹具中的模拟而不是获取模拟对象:
fixture.Freeze<Mock<ISettings>>(f => f.Do(m => m.Setup(s => s.Get(settingKey)).Returns(xmlString)));
Run Code Online (Sandbox Code Playgroud)
果然这完全正常 - 只要我SettingMappingXml明确地调用构造函数而不依赖它CreateAnonymous().
简单地说,我不明白它为什么会以它显然的方式工作,因为它违背了我能想到的任何逻辑.通常情况下,我会怀疑库中有一个错误,但这是一个非常基本的东西,我相信其他人会遇到这个问题,而且很久以来就会发现并修复它.更重要的是,了解马克对测试和DI的刻苦态度,这不是无意的.
这反过来意味着我必须错过一些相当基本的东西.如何将AutoFixture创建的SUT与预先配置的模拟对象作为依赖项?我现在唯一确定的是我需AutoMoqCustomization要这样做,所以我没有为此配置任何东西ITracingService.
AutoFixture/AutoMoq包是2.14.1,Moq是3.1.416.3,全部来自NuGet..NET版本为4.5(与VS2012一起安装),VS2012和2010中的行为相同.
在撰写这篇文章时,我发现有些人遇到了Moq 4.0和程序集绑定重定向的问题,所以我通过将AutoFixture.AutoMoq安装到"干净"的项目中,精心清除了Moq 4的任何实例的解决方案并安装了Moq 3.1.但是,我的测试行为保持不变.
感谢您的任何指示和解释.
更新:这是Mark要求的构造函数代码:
public SettingMappingXml(ISettings settingSource, ITracingService tracing)
{
this._settingSource = settingSource;
this._tracing = tracing;
this.SettingKey = "gcCreditApplicationUsdFieldMappings";
}
Run Code Online (Sandbox Code Playgroud)
为了完整起见,该GetXml()方法如下所示:
public XElement GetXml()
{
int errorCode = 10600;
try
{
string mappingSetting = this._settingSource.Get(this.SettingKey);
errorCode++;
XElement mappingXml = XElement.Parse(mappingSetting);
errorCode++;
return mappingXml;
}
catch (Exception e)
{
this._tracing.Trace(errorCode, e.Message);
throw;
}
}
Run Code Online (Sandbox Code Playgroud)
SettingKey 只是一个自动财产.
Mar*_*ann 15
假设该SettingKey属性定义如下,我现在可以重现该问题:
public string SettingKey { get; set; }
Run Code Online (Sandbox Code Playgroud)
注意到注入SettingMappingXml实例的Test Doubles完全正常,但由于它SettingKey是可写的,因此AutoFixture的自动属性功能会启动并修改该值.
考虑以下代码:
var fixture = new Fixture().Customize(new AutoMoqCustomization());
var sut = fixture.CreateAnonymous<SettingMappingXml>();
Console.WriteLine(sut.SettingKey);
Run Code Online (Sandbox Code Playgroud)
这打印出这样的东西:
SettingKey83b75965-2886-4308-bcc4-eb0f8e63de09
尽管所有测试双打都被正确注入,但是Setup不满足该方法的期望.
有很多方法可以解决这个问题.
保护不变量
解决此问题的正确方法是使用单元测试和AutoFixture作为反馈机制.这是GOOS的关键点之一:单元测试的问题通常是设计缺陷的症状,而不是单元测试(或AutoFixture)本身的错误.
在这种情况下,它向我表明设计不够万无一失.客户可以随意操纵是否真的合适SettingKey?
作为最低限度,我建议使用这样的替代实现:
public string SettingKey { get; private set; }
Run Code Online (Sandbox Code Playgroud)
随着这种变化,我的责备通过了.
省略SettingKey
如果您不能(或不会)更改您的设计,您可以指示AutoFixture跳过设置SettingKey属性:
IMappingXml sut = fixture
.Build<SettingMappingXml>()
.Without(s => s.SettingKey)
.CreateAnonymous();
Run Code Online (Sandbox Code Playgroud)
就个人而言,我发现Build每次需要特定类的实例时都必须编写表达式会适得其反.您可以SettingMappingXml从实际实例化中分离实例的创建方式:
fixture.Customize<SettingMappingXml>(
c => c.Without(s => s.SettingKey));
IMappingXml sut = fixture.CreateAnonymous<SettingMappingXml>();
Run Code Online (Sandbox Code Playgroud)
为了更进一步,您可以Customize在定制中封装该方法调用.
public class SettingMappingXmlCustomization : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customize<SettingMappingXml>(
c => c.Without(s => s.SettingKey));
}
}
Run Code Online (Sandbox Code Playgroud)
这要求您Fixture使用该自定义创建实例:
IFixture fixture = new Fixture()
.Customize(new SettingMappingXmlCustomization())
.Customize(new AutoMoqCustomization());
Run Code Online (Sandbox Code Playgroud)
一旦您获得超过两个或三个自定义项链,您可能会厌倦一直编写该方法链.是时候将这些自定义封装到特定库的一组约定中:
public class TestConventions : CompositeCustomization
{
public TestConventions()
: base(
new SettingMappingXmlCustomization(),
new AutoMoqCustomization())
{
}
}
Run Code Online (Sandbox Code Playgroud)
这使您始终可以Fixture像这样创建实例:
IFixture fixture = new Fixture().Customize(new TestConventions());
Run Code Online (Sandbox Code Playgroud)
它TestConventions为您提供了一个中心位置,您可以在需要时偶尔修改测试套件的约定.它降低了单元测试的可维护性税,并有助于使生产代码的设计更加一致.
最后,因为它看起来好像你正在使用xUnit.net,你可以利用AutoFixture的xUnit.net集成,但在你这样做之前,你需要使用一种不那么强制的操作方式Fixture.事实证明,创建,配置和注入ISettingsTest Double 的代码是如此惯用,它有一个名为Freeze的快捷方式:
fixture.Freeze<Mock<ISettings>>()
.Setup(s => s.Get(settingKey)).Returns(xmlString);
Run Code Online (Sandbox Code Playgroud)
有了这个,下一步是定义一个自定义AutoDataAttribute:
public class AutoConventionDataAttribute : AutoDataAttribute
{
public AutoConventionDataAttribute()
: base(new Fixture().Customize(new TestConventions()))
{
}
}
Run Code Online (Sandbox Code Playgroud)
您现在可以将测试减少到最基本的部分,消除所有噪音,使测试能够简洁地表达重要的内容:
[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));
}
Run Code Online (Sandbox Code Playgroud)
其他选择
要使原始测试通过,您还可以完全关闭自动属性:
fixture.OmitAutoProperties = true;
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
3719 次 |
| 最近记录: |