簡體   English   中英

AutoFixture / AutoMoq忽略注入的實例/凍結模擬

[英]AutoFixture/AutoMoq ignores injected instance/frozen mock

現在找到解決方案的簡短內容:

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

這里的故事很簡單 - 確保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));
}

我希望CreateAnonymous<SettingMappingXml>()在檢測到ISettings構造函數參數時,注意到已為該接口注冊了一個具體實例並注入了 - 但是,它不會這樣做,而是創建一個新的匿名實現。

這特別令人困惑,因為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()



簡單地說,我不明白它為什么會以它顯然的方式工作,因為它違背了我能想到的任何邏輯。 通常情況下,我會懷疑庫中有一個錯誤,但這是一個非常基本的東西,我相信其他人會遇到這個問題,而且很久以來就會發現並修復它。 更重要的是,了解馬克對測試和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";
}

為了完整GetXml()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; }

注意到注入SettingMappingXml實例的Test Doubles完全沒問題,但由於SettingKey是可寫的,因此AutoFixture的自動屬性功能會啟動並修改該值。

考慮以下代碼:

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

這打印出這樣的東西:

SettingKey83b75965-2886-4308-bcc4-eb0f8e63de09

即使所有測試雙打都已正確注入,但不滿足Setup方法的期望。

有很多方法可以解決這個問題。

保護不變量

解決此問題的正確方法是使用單元測試和AutoFixture作為反饋機制。 這是GOOS的關鍵點之一:單元測試的問題通常是設計缺陷的症狀,而不是單元測試(或AutoFixture)本身的錯誤。

在這種情況下,它向我表明設計不夠萬無一失 客戶端可以隨意操作SettingKey是否合適?

作為最低限度,我建議使用這樣的替代實現:

public string SettingKey { get; private set; }

隨着這種變化,我的責備通過了。

省略SettingKey

如果您不能(或不會)更改設計,可以指示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方法調用封裝在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的xUnit.net集成 ,但在你這樣做之前,你需要使用一種不那么強制的操作Fixture 事實證明,創建,配置和注入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;

在第一個測試中,您可以使用AutoMoqCustomization創建Fixture類的實例:

var fixture = new Fixture()
    .Customize(new AutoMoqCustomization());

然后,唯一的變化是:

第1步

// The following line:
Mock<ISettings> settingsMock = new Mock<ISettings>();
// Becomes:
Mock<ISettings> settingsMock = fixture.Freeze<Mock<ISettings>>();

第2步

// The following line:
ITracingService tracing = new Mock<ITracingService>().Object;
// Becomes:
ITracingService tracing = fixture.Freeze<Mock<ITracingService>>().Object;

第3步

// The following line:
IMappingXml sut = new SettingMappingXml(settings, tracing);
// Becomes:
IMappingXml sut = fixture.CreateAnonymous<SettingMappingXml>();

而已!


下面是它的工作原理:

在內部, Freeze創建所請求類型的實例(例如Mock<ITracingService> ),然后注入 ,以便在您再次請求時始終返回該實例。

這就是我們在Step 1Step 2所做的。

Step 3我們請求一個SettingMappingXml類型的實例,它依賴於ISettingsITracingService 由於我們使用Auto Mocking, Fixture類將為這些接口提供模擬。 但是,我們之前已經為它們注入Freeze因此現在已經自動提供已經創建的模擬。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM