AutoFixture/AutoMoq 忽略注入实例/冻结模拟

2024-01-05

现在已经找到解决方案,简短的结论是:

AutoFixture 返回冻结模拟就好了;我的 sut 也是由 AutoFixture 生成的,它只有一个公共属性,该属性具有对测试很重要的本地默认值,并且 AutoFixture 设置为新值。除此之外,从马克的回答中还有很多东西值得学习。

原问题:

我昨天开始尝试 AutoFixture 进行 xUnit.net 测试,这些测试都带有起订量。我希望替换一些 Moq 内容或使其更易于阅读,并且我对在 SUT Factory 功能中使用 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与正确密钥(硬编码/属性注入)的依赖关系并将结果作为XElement. The 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));
}

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

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

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

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

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

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

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



简而言之,我不明白为什么它会以它明显的方式工作,因为它违背了我能想到的任何逻辑。 通常我会怀疑库中存在错误,但这是一个非常基本的问题,我确信其他人也会遇到这个问题,而且它很早就被发现并修复了。更重要的是,了解 Mark 对测试和 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 和程序集绑定重定向时遇到问题,因此我小心翼翼地清除了解决方案中任何 Moq 4 实例,并通过将 AutoFixture.AutoMoq 安装到“干净”项目中来安装 Moq 3.1。但是,我的测试行为保持不变。

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

Update:这是马克要求的构造函数代码:

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; }

发生的情况是测试双打 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。事实证明,创建、配置和注入的代码ISettingsTest 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;
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

AutoFixture/AutoMoq 忽略注入实例/冻结模拟 的相关文章

  • 调用 McAfee 病毒扫描引擎

    我收到客户的请求 要求使用他们服务器上的 McAfee 病毒扫描将病毒扫描集成到应用程序中 我做了一些调查 发现 McScan32 dll 是主要的扫描引擎 它导出各种看起来有用的函数 我还发现提到了 McAfee Scan Engine
  • 为什么 GCC 不允许我创建“内联静态 std::stringstream”?

    我将直接前往 MCVE include
  • 如何从本机 C(++) DLL 调用 .NET (C#) 代码?

    我有一个 C app exe 和一个 C my dll my dll NET 项目链接到本机 C DLL mynat dll 外部 C DLL 接口 并且从 C 调用 C DLL 可以正常工作 通过使用 DllImport mynat dl
  • 如何连接重叠的圆圈?

    我想在视觉上连接两个重叠的圆圈 以便 becomes 我已经有部分圆的方法 但现在我需要知道每个圆的重叠角度有多大 但我不知道该怎么做 有人有主意吗 Phi ArcTan Sqrt 4 R 2 d 2 d HTH Edit 对于两个不同的半
  • 对类 static constexpr 结构的未定义引用,g++ 与 clang

    这是我的代码 a cp p struct int2 int x y struct Foo static constexpr int bar1 1 static constexpr int2 bar2 1 2 int foo1 return
  • WPF 数据绑定到复合类模式?

    我是第一次尝试 WPF 并且正在努力解决如何将控件绑定到使用其他对象的组合构建的类 例如 如果我有一个由两个单独的类组成的类 Comp 为了清楚起见 请注意省略的各种元素 class One int first int second cla
  • WcfSvcHost 的跨域异常

    对于另一个跨域问题 我深表歉意 我一整天都在与这个问题作斗争 现在已经到了沸腾的地步 我有一个 Silverlight 应用程序项目 SLApp1 一个用于托管 Silverlight SLApp1 Web 的 Web 项目和 WCF 项目
  • 结构体的内存大小不同?

    为什么第一种情况不是12 测试环境 最新版本的 gcc 和 clang 64 位 Linux struct desc int parts int nr sizeof desc Output 16 struct desc int parts
  • 为什么这个字符串用AesCryptoServiceProvider第二次解密时不相等?

    我在 C VS2012 NET 4 5 中的文本加密和解密方面遇到问题 具体来说 当我加密并随后解密字符串时 输出与输入不同 然而 奇怪的是 如果我复制加密的输出并将其硬编码为字符串文字 解密就会起作用 以下代码示例说明了该问题 我究竟做错
  • ExpectedFailure 被计为错误而不是通过

    我在用着expectedFailure因为有一个我想记录的错误 我现在无法修复 但想将来再回来解决 我的理解expectedFailure是它会将测试计为通过 但在摘要中表示预期失败的数量为 x 类似于它如何处理跳过的 tets 但是 当我
  • 实例化类时重写虚拟方法

    我有一个带有一些虚函数的类 让我们假设这是其中之一 public class AClassWhatever protected virtual string DoAThingToAString string inputString retu
  • Cucumber Java 与 Spring Boot 集成 - Spring @Autowired 抛出 NullPointer 异常

    我正在为 Spring boot 应用程序编写 cucumber java 单元测试来测试每个功能 当我与 Spring Boot 集成时 Autowired 类抛出 NullPointer 异常 Spring Boot应用程序类 Spri
  • 如何在当前 Visual Studio 主机内的 Visual Studio 扩展中调试使用 Roslyn 编译的代码?

    我有一个 Visual Studio 扩展 它使用 Roslyn 获取当前打开的解决方案中的项目 编译它并从中运行方法 程序员可以修改该项目 我已从当前 VisualStudioWorkspace 成功编译了 Visual Studio 扩
  • C# 动态/expando 对象的深度/嵌套/递归合并

    我需要在 C 中 合并 2 个动态对象 我在 stackexchange 上找到的所有内容仅涵盖非递归合并 但我正在寻找能够进行递归或深度合并的东西 非常类似于jQuery 的 extend obj1 obj2 http api jquer
  • 为什么使用小于 32 位的整数?

    我总是喜欢使用最小尺寸的变量 这样效果就很好 但是如果我使用短字节整数而不是整数 并且内存是 32 位字可寻址 这真的会给我带来好处吗 编译器是否会做一些事情来增强内存使用 对于局部变量 它可能没有多大意义 但是在具有数千甚至数百万项的结构
  • 使用特定参数从 SQL 数据库填充组合框

    我在使用参数从 sql server 获取特定值时遇到问题 任何人都可以解释一下为什么它在 winfom 上工作但在 wpf 上不起作用以及我如何修复它 我的代码 private void UpdateItems COMBOBOX1 Ite
  • 对于某些 PDF 文件,LoadIFilter() 返回 -2147467259

    我正在尝试使用 Adob e IFilter 搜索 PDF 文件 我的代码是用 C 编写的 我使用 p invoke 来获取 IFilter 的实例 DllImport query dll SetLastError true CharSet
  • 为什么C++代码执行速度比java慢?

    我最近用 Java 编写了一个计算密集型算法 然后将其翻译为 C 令我惊讶的是 C 的执行速度要慢得多 我现在已经编写了一个更短的 Java 测试程序和一个相应的 C 程序 见下文 我的原始代码具有大量数组访问功能 测试代码也是如此 C 的
  • 类型或命名空间“MyNamespace”不存在等

    我有通常的类型或命名空间名称不存在错误 除了我引用了程序集 using 语句没有显示为不正确 并且我引用的类是公共的 事实上 我在不同的解决方案中引用并使用相同的程序集来执行相同的操作 并且效果很好 顺便说一句 这是VS2010 有人有什么
  • Mono 应用程序在非阻塞套接字发送时冻结

    我在 debian 9 上的 mono 下运行一个服务器应用程序 大约有 1000 2000 个客户端连接 并且应用程序经常冻结 CPU 使用率达到 100 我执行 kill QUIT pid 来获取线程堆栈转储 但它总是卡在这个位置

随机推荐

  • iPhone:自动增加徽章计数器[重复]

    这个问题在这里已经有答案了 可能的重复 推送通知徽章自动递增 https stackoverflow com questions 1942605 push notification badge auto increment 我已经为我的 i
  • “setInterval”与“setTimeout”[重复]

    这个问题在这里已经有答案了 之间的主要区别是什么 设置时间间隔 https developer mozilla org En window setInterval and 设置超时时间 https developer mozilla org
  • Angular 路由器链接活动嵌套菜单

    我正在尝试制作一个带有角度路线的嵌套菜单 我需要的是 如果嵌套路由处于活动状态 则将类应用于嵌套路由 如果其子组件处于活动状态 则将类应用于父组件 我该如何实现这一目标 现在 我正在构建递归菜单 以便在需要多层嵌套时易于使用 组件 html
  • jquery如何使用多个ajax调用一个接一个结束

    我在移动应用程序中 使用多个 Ajax 调用从 Web 服务器接收数据 如下所示 function get json document ready function ajax url http www xxxxxxxxxxxxx data
  • 如何使用 CodeFirst 方法在 IdentityServer 4 的 Client 表中添加新列?

    我正在实施 IdentityServer4 我尝试向 Client 添加新字段 CustomerId 在进行迁移时 它会创建名为 Client 的新表 而不是在 Clients 表中添加新列 namespace xx xx Models T
  • Android资源链接失败

    有谁知道这个错误是什么以及我该如何解决这个问题 我正在尝试从 Mumble 打开源代码并遇到此错误 Android resource linking failed Output C Users NP Desktop Plumble Lega
  • JSONModel 中“metadataLoaded”的替代品是什么? [复制]

    这个问题在这里已经有答案了 In sap ui model odata v2 ODataModel 有一个metadataLoaded https openui5 hana ondemand com docs api symbols sap
  • Usb4java 和 Windows 7 64 位操作系统我收到 USB 错误 8 或 12

    我正在尝试让 usb4java 包工作 我获得了基本连接 但当我尝试获取产品 序列号或制造商时 我收到 USB 错误 12 或 8 我想我可能把库弄乱了 我使用的是 Win 7 Pro 64 位操作系统 Service Pack 1 Ecl
  • href="#" 转到页面顶部 - 预防? [复制]

    这个问题在这里已经有答案了 我有一个包含一些 jQuery 函数的页面 页面上的 HTML 看起来像这样 a href class service Open a 当我单击 打开 按钮时 隐藏的面板会滑出 jQuery 本身工作得很好 但是当
  • 在 Xamarin.Forms 应用程序的 android 部分中禁用 defaultFocusHighlightEnabled

    在 android 8 设备中运行的 xamarin forms 应用程序中 我想在使用物理键盘时禁用控件突出显示 这是新的 android 8 行为 android 文档说我应该为此设置 android defaultFocusHighl
  • 使用 MonoTouch 部署 IOS6 应用程序时出错

    我正在尝试让 iOS6 在我的笔记本电脑上运行 我正在运行 xcode 4 5 和最新的 MonoTouch 每当我尝试部署到手机时 都会收到错误消息 Installing application Installation failed E
  • elementtree注册命名空间错误

    我尝试用这个注册名称空间 ET register namespace inv http www stormware cz schema version 2 invoice xsd 但它不起作用 Traceback most recent c
  • Android 按钮不起作用的波纹

    我给按钮背景赋予的波纹效果不起作用 它只是切换颜色 设备版本是5 1 1 请帮助我 波纹 xml
  • 在反应组件中使用异步等待

    因此 我创建了一个组件 可以使用我提供的道具发出发布请求 虽然我熟悉异步等待 但由于某种原因 我似乎无法让它返回已履行承诺的实际值 而是只是等待 我尝试使用更多函数进行包装 因为我了解承诺并未得到解决 我觉得我失去了一些东西 下面是我的代码
  • 如何使用 Selenium ChromeDriver 执行右键单击?

    我一直在寻找这个问题 但找不到Python的答案 是否可以模拟右键单击 或通过 selenium chromedriver 打开上下文菜单 我见过 Java 和其他一些语言的选项 但从未见过 Python 我需要做什么才能模拟右键单击链接或
  • 我们可以使用 MS WebDeploy 创建一个新网站吗

    我已经阅读了许多关于通过 VS2012 和通过命令行进行 WebDeploy 的帖子 文档页面等 在所有部署方案中 我注意到目标站点应该已经存在于目标计算机 IIS 上 是否有一个选项可以创建一个包如果站点不存在则创建该站点在目标 托管 机
  • 处理 RDFa 中“rel”的非语义使用

    我使用 RDFa 将链接数据添加到我的网页 我也偶尔使用rel用于非语义目的的各种标签中的属性 例如触发 javascript 工具提示 我想知道我可以做些什么来区分用途 例如 我的社交网络图标周围有 RDFa 如下所示 a alt twi
  • 在 OpenShift 上使用密码保护 django 应用程序的非常简单的方法

    有没有一种非常简单的方法可以在测试时使用 htaccess 创建密码访问权限 我不想做任何会干扰应用程序的事情 有没有办法在 OpenShift 中做到这一点 您可以使用 htaccess 和 htpasswd 进行密码保护 以避免在网站尚
  • 在 zsh 中,如何对程序的退出状态执行条件?

    我想做类似的事情 if git status gt dev null then echo is a git repo else echo is not a git repo fi 但我不知道如何检查退出状态 我该如何解决 变量 包含最后命令
  • AutoFixture/AutoMoq 忽略注入实例/冻结模拟

    现在已经找到解决方案 简短的结论是 AutoFixture 返回冻结模拟就好了 我的 sut 也是由 AutoFixture 生成的 它只有一个公共属性 该属性具有对测试很重要的本地默认值 并且 AutoFixture 设置为新值 除此之外