gpt4 book ai didi

c# - AutoFixture/AutoMoq 忽略注入(inject)实例/卡住模拟

转载 作者:可可西里 更新时间:2023-11-01 08:38:48 25 4
gpt4 key购买 nike

既然已经找到了解决方案,那么简短的总结:

AutoFixture 返回卡住模拟就好了;我的 sut 也是由 AutoFixture 生成的,它只有一个公共(public)属性,其中包含对测试很重要的本地默认值,并且 AutoFixture 设置为新值。从 Mark 的回答中可以学到很多东西。

原始问题:

我昨天开始尝试使用 AutoFixture 进行我的 xUnit.net 测试,这些测试都具有最小起订量。我希望替换一些最小起订量的东西或使其更易于阅读,而且我对在 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));
}

这里的故事很简单 - 确保 SettingMappingXml查询 ISettings具有正确 key (硬编码/属性注入(inject))的依赖项并将结果作为 XElement 返回. ITracingService仅当出现错误时才相关。

我试图做的是摆脱显式创建 ITracingService 的需要对象,然后手动注入(inject)依赖项(不是因为这个测试太复杂,而是因为它足够简单,可以尝试并理解它们)。

输入 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));
}

我希望 CreateAnonymous<SettingMappingXml>() , 在检测到 ISettings构造函数参数,以注意已为该接口(interface)注册了一个具体实例并注入(inject)它 - 然而,它并没有这样做,而是创建了一个新的匿名实现。

这特别令人困惑,因为 fixture.CreateAnonymous<ISettings>()确实返回我的实例 -

IMappingXml sut = new SettingMappingXml(fixture.CreateAnonymous<ISettings>(), fixture.CreateAnonymous<ITracingService>());

使测试完全绿色,这行正是我在实例化 SettingMappingXml 时期望 AutoFixture 在内部执行的操作.

然后是卡住组件的概念,所以我继续卡住夹具中的模拟而不是获取模拟对象:

fixture.Freeze<Mock<ISettings>>(f => f.Do(m => m.Setup(s => s.Get(settingKey)).Returns(xmlString)));

果然这工作得很好——只要我调用 SettingMappingXml显式构造函数并且不依赖 CreateAnonymous() .



简而言之,我不明白为什么它会以它明显的方式工作,因为它违背了我能想到的任何逻辑。通常我会怀疑库中的错误,但这是非常基本的事情,我相信其他人会遇到这个问题,而且它早就被发现并修复了。更重要的是,了解 Mark 对测试和 DI 的孜孜不倦的态度,这不是无意的。

这反过来意味着我一定遗漏了一些相当基本的东西。我怎样才能让我的 SUT 由 AutoFixture 创建,并将预配置的模拟对象作为依赖项?我现在唯一确定的是我需要 AutoMoqCustomization所以我不必为 ITracingService 配置任何东西.

AutoFixture/AutoMoq 包是 2.14.1,Moq 是 3.1.416.3,全部来自 NuGet。 .NET 版本为 4.5(与 VS2012 一起安装),VS2012 和 2010 中的行为相同。

在写这篇文章时,我发现有些人遇到了 Moq 4.0 和程序集绑定(bind)重定向的问题,所以我小心翼翼地清除了我的解决方案中的任何 Moq 4 实例,并通过将 AutoFixture.AutoMoq 安装到“clean”中来安装 Moq 3.1项目。但是,我的测试行为保持不变。

感谢您的任何指点和解释。

更新:这是 Mark 要求的构造函数代码:

public SettingMappingXml(ISettings settingSource, ITracingService tracing)
{
this._settingSource = settingSource;
this._tracing = tracing;

this.SettingKey = "gcCreditApplicationUsdFieldMappings";
}

为了完整起见,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;
}
}

SettingKey只是一个自动属性。

最佳答案

假设 SettingKey 属性定义如下,我现在可以重现问题:

public string SettingKey { get; set; }

发生的事情是 Test Doubles注入(inject)到 SettingMappingXml 实例中非常好,但是因为 SettingKey 是可写的,AutoFixture 的自动属性功能会启动并修改值。

考虑这段代码:

var fixture = new Fixture().Customize(new AutoMoqCustomization());
var sut = fixture.CreateAnonymous<SettingMappingXml>();
Console.WriteLine(sut.SettingKey);

这会打印出如下内容:

SettingKey83b75965-2886-4308-bcc4-eb0f8e63de09

即使所有测试替身都已正确注入(inject),Setup 方法中的预期仍未满足。

有很多方法可以解决这个问题。

保护不变量

解决此问题的正确方法是使用单元测试和 AutoFixture 作为反馈机制。这是GOOS中的关键点之一。 : 单元测试的问题通常是设计缺陷的症状,而不是单元测试(或 AutoFixture)本身的错误。

在这种情况下,它向我表明 the design isn't fool-proof enough .客户端可以随意操作 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 方法调用封装在 a Customization 中.

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());

TestConventions 为您提供了一个中心位置,您可以在需要时偶尔修改测试套件的约定。它减少了单元测试的可维护性负担,并有助于使生产代码的设计更加一致。

最后,由于看起来您正在使用 xUnit.net,因此您可以利用 AutoFixture's xUnit.net integration ,但在执行此操作之前,您需要使用命令式较少的方式来操作 Fixture。事实证明,创建、配置和注入(inject) ISettings Test Double 的代码非常地道,它有一个名为 Freeze 的快捷方式。 :

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;

关于c# - AutoFixture/AutoMoq 忽略注入(inject)实例/卡住模拟,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13484471/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com