AutoFixture / AutoMoq忽略注入实例/冷冻模拟 [英] AutoFixture/AutoMoq ignores injected instance/frozen mock

查看:411
本文介绍了AutoFixture / AutoMoq忽略注入实例/冷冻模拟的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

现在的短外卖该解决方案已被发现:

AutoFixture冻结模拟就好了回报;我这也被AutoFixture产生SUT刚刚与一个本地默认,这是为测试和AutoFixture设置为一个新的价值的重要公共财产。有很多东西要学超出了马克的回答

AutoFixture returns frozen the mock just fine; my sut that was also generated by AutoFixture just had a public property with a local default that was important for the test and that AutoFixture set to a new value. There is much to learn beyond that from Mark's answer.

原题:

我开始尝试AutoFixture昨天有起订量遍他们我xUnit.net测试。我希望替换一些的起订量的东西或使其更容易阅读,我在SUT工厂的产能利用AutoFixture特别感兴趣。

I started trying out AutoFixture yesterday for my xUnit.net tests that have Moq all over them. I was hoping to replace some of the Moq stuff or make it easier to read, and I'm especially interested in using AutoFixture in the SUT Factory capacity.

我自己武装有一些上AutoMocking马克·塞曼的博客文章,试图从那里工作,但我没有得到很远。

I armed myself with a few of Mark Seemann's blog posts on AutoMocking and tried to work from there, but I didn't get very far.

这是我的测试看起来像没有AutoFixture

This is what my test looked like without 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 。在 ITracingService 相关仅如果有一个错误。

The story here is simple enough - make sure that SettingMappingXml queries the ISettings dependency with the correct key (which is hard coded/property injected) and returns the result as an XElement. The ITracingService is relevant only if there's an error.

我试图做的就是摆脱的需要明确创建 ITracingService 对象,然后手动注入的依赖关系(不是因为这个测试是太复杂了,而是因为它是很简单的尝试的东西出来,并理解它们)。

What I was trying to do is get rid of the need to explicitly create the ITracingService object and then manually inject the dependencies (not because this test is too complex, but because it is simple enough to try things out and understand them).

输入AutoFixture - 第一次尝试:

Enter AutoFixture - first attempt:

[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 构造函数的参数,要注意到一个具体的实例已注册该接口并注入了 - 但是,它不。这样做,而是创建了一个新的匿名实施

I would expect CreateAnonymous<SettingMappingXml>(), upon detection of the ISettings constructor parameter, to notice that a concrete instance has been registered for that interface and inject that - however, it doesn't do that but instead creates a new anonymous implementation.

这是因为 fixture.CreateAnonymous<特别混乱; ISettings>()确实返回我的实例 -

This is especially confusing as fixture.CreateAnonymous<ISettings>() does indeed return my instance -

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



使得测试完全绿色,此行正是我所预料AutoFixture在内部完成实例化时 SettingMappingXml

再有就是冻结组件的概念,所以我继续只是冻结在夹具模拟而不是得到的嘲笑对象:

Then there's the concept of freezing a component, so I went ahead just freezing the mock in the fixture instead of getting the mocked object:

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



果然这工作完全正常 - 只要我称之为 SettingMappingXml 构造明确,不依赖于 CreateAnonymous()

Sure enough this works perfectly fine - as long as I call the SettingMappingXml constructor explicitly and don't rely on CreateAnonymous().




简单地说,我不明白为什么它的工作原理它显然做的方式,因为它违背了我能变出任何逻辑。
通常我会怀疑在图书馆中的错误,但这是如此基本,我相信其他人会碰到这一点,它会长期被发现并修复。更重要的是,知道马克的孜孜以求的测试方法和DI,这不可能是无意的。



Simply put, I don't understand why it works the way it apparently does, as it goes against any logic I can conjure up. Normally I would suspect a bug in the library, but this is something so basic that I'm sure others would have run into this and it would long have been found and fixed. What's more, knowing Mark's assiduous approach to testing and DI, this can't be unintentional.

这也就意味着我必须失去了一些东西,而小学。如何我有我的SUT由AutoFixture与预先配置的嘲笑对象,作为一个依赖产生的?我敢肯定,对现在的唯一的事情是我需要的 AutoMoqCustomization 所以我没有配置为任何 ITracingService

That in turn means I must be missing something rather elementary. How can I have my SUT created by AutoFixture with a preconfigured mocked object as a dependency? The only thing I'm sure about right now is that I need the AutoMoqCustomization so I don't have to configure anything for the ITracingService.

AutoFixture / AutoMoq包2.14.1,起订量为3.1.416.3,都来自的NuGet。 .NET版本是4.5(与VS2012安装),行为是在VS2012和2010年相同。

AutoFixture/AutoMoq packages are 2.14.1, Moq is 3.1.416.3, all from NuGet. .NET version is 4.5 (installed with VS2012), behavior is the same in VS2012 and 2010.

虽然写这篇文章,我发现有些人具有起订量问题4.0和组装绑定重定向,所以我认真清除我的起订量4的任何实例的解决方案,并有3.1起订量通过安装AutoFixture.AutoMoq到干净的项目安装。但是,我的测试的行为保持不变。

While writing this post, I discovered that some people were having problems with Moq 4.0 and assembly binding redirects, so I meticulously purged my solution of any instances of Moq 4 and had Moq 3.1 installed by installing AutoFixture.AutoMoq into "clean" projects. However, the behavior of my test remains unchanged.

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

Thank you for any pointers and explanations.

更新:这里的构造函数代码马克问:

Update: Here's the constructor code Mark asked for:

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

    this.SettingKey = "gcCreditApplicationUsdFieldMappings";
}

和完整性,在的getXML()方法是这样的:

And for completeness, the GetXml() method looks like this:

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 is just an automatic property.

推荐答案

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

Assuming that the SettingKey property is defined as follows, I can now reproduce the issue:

public string SettingKey { get; set; }



会发生什么事是,的测试双打的注入SettingMappingXml实例是完全正常的,但因为 SettingKey 是可写的,AutoFixture的自动属性功能在踢和修改。值

What happens is that the Test Doubles injected into the SettingMappingXml instance are perfectly fine, but because the SettingKey is writable, AutoFixture's Auto-properties feature kicks in and modifies the value.

考虑以下代码:

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

这将打印这样的:

SettingKey83b75965-2886-4308-bcc4-eb0f8e63de09

SettingKey83b75965-2886-4308-bcc4-eb0f8e63de09

尽管所有的试验双打正确注入的期望在设置方法不符合。

Even though all the Test Doubles are properly injected, the expectation in the Setup method isn't met.

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

There are many ways to address this issue.

保护不变

要解决此问题,正确的方法是使用单元测试和AutoFixture作为反馈机制。这是全球海洋观测系统中的关键点之一:单元测试问题往往是一个关于设计缺陷的症状,而不是单元测试的故障(或AutoFixture)本身。

The proper way to resolve this issue is to use the unit test and AutoFixture as a feedback mechanism. This is one of the key points in GOOS: problems with unit tests are often a symptom about a design flaw rather than the fault of the unit test (or AutoFixture) itself.

在这种情况下,它表明我的设计也不是傻子,足以证明。是不是真的适合客户端可以操纵 SettingKey 的意愿?

In this case it indicates to me that the design isn't fool-proof enough. Is it really appropriate that a client can manipulate the SettingKey at will?

作为最低限度,我会建议另一种实现是这样的:

As a bare minimum, I would recommend an alternative implementation like this:

public string SettingKey { get; private set; }

通过这种变化,我的摄制通行证。

With that change, my repro passes.

省略SettingKey

如果你不能(或不愿)改变你的设计,你可以指示AutoFixture跳过设置 SettingKey 属性:

If you can't (or won't) change your design, you can instruct AutoFixture to skip setting the SettingKey property:

IMappingXml sut = fixture
    .Build<SettingMappingXml>()
    .Without(s => s.SettingKey)
    .CreateAnonymous();



就个人而言,我觉得适得其反必须写一个构建表达每次我需要一个特定的类的实例的时间。您可以断开如何使用 SettingMappingXml 实例是从实际的实例创建:

Personally, I find it counterproductive to have to write a Build expression every time I need an instance of a particular class. You can decouple how the SettingMappingXml instance are created from the actual instantiation:

fixture.Customize<SettingMappingXml>(
    c => c.Without(s => s.SettingKey));
IMappingXml sut = fixture.CreateAnonymous<SettingMappingXml>();

要借此进一步,可以封装的自定义 一个定制。

To take this further, you can encapsulate that Customize method call in a Customization.

public class SettingMappingXmlCustomization : ICustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Customize<SettingMappingXml>(
            c => c.Without(s => s.SettingKey));
    }
}

这需要你创建灯具实例与定制:

This requires you to create your Fixture instance with that Customization:

IFixture fixture = new Fixture()
    .Customize(new SettingMappingXmlCustomization())
    .Customize(new AutoMoqCustomization());



一旦你获得超过两个或三个定制连锁,你可能会感到厌倦编写方法链每时每刻。现在是时候来封装这些自定义成一组约定,为您的特定库:

Once you get more than two or three Customizations to chain, you may get tired of writing that method chain all the time. It's time to encapsulate those Customizations into a set of conventions for your particular library:

public class TestConventions : CompositeCustomization
{
    public TestConventions()
        : base(
            new SettingMappingXmlCustomization(),
            new AutoMoqCustomization())
    {
    }
}

这使您可以随时创建灯具例如像这样的:

This enables you to always create the Fixture instance like this:

IFixture fixture = new Fixture().Customize(new TestConventions());



TestConventions 为您提供了一个集中的地方在哪里你可以去偶尔修改约定测试套件的时候,你需要这样做。它减少了单元测试的可维护性税和有助于保持您的生产代码的设计更加一致。

The TestConventions gives you a central place where you can go and occasionally modify your conventions for the test suite when you need to do so. It reduces the maintainability tax of your unit tests and helps keep the design of your production code more consistent.

最后,因为它看起来好像你正在使用xUnit.net,你可以利用 AutoFixture的xUnit.net整合,但在此之前,你可能需要使用操纵灯具较少势在必行风格。事实证明,它创建,配置和注入的 ISettings 测试替身是如此地道,它有一个名为的冻结

Finally, since it looks as though you are using xUnit.net, you could utilize AutoFixture's xUnit.net integration, but before you do that you'd need to use a less imperative style of manipulating the Fixture. It turns out that the code which creates, configures and injects the ISettings Test Double is so idiomatic that it has a shortcut called Freeze:

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

使用该就位,下一步骤是定义定制AutoDataAttribute:

With that in place, the next step is to define a custom AutoDataAttribute:

public class AutoConventionDataAttribute : AutoDataAttribute
{
    public AutoConventionDataAttribute()
        : base(new Fixture().Customize(new TestConventions()))
    {
    }
}

现在,您可以测试减少到最基本的要素,摆脱所有的噪音,使测试只简洁地表达什么事情:

You can now reduce the test to the bare essentials, getting rid of all the noise, enabling the test to succinctly express only what matters:

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

其他选项

为使原来的测试通不过,你也可以只关掉自动性能完全:

To make the original test pass, you could also just switch off Auto-properties entirely:

fixture.OmitAutoProperties = true;

这篇关于AutoFixture / AutoMoq忽略注入实例/冷冻模拟的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆