12

解決策が見つかったので、簡単に説明します。

AutoFixtureは、モックを正常にフリーズして返します。AutoFixtureによって生成された私のsutには、テストにとって重要なローカルデフォルトのパブリックプロパティがあり、AutoFixtureが新しい値に設定されていました。マークの答えからそれを超えて学ぶことがたくさんあります。

元の質問:

私は昨日、MoqがいたるところにあるxUnit.netテストでAutoFixtureを試し始めました。Moqのものの一部を置き換えたり、読みやすくしたりしたいと思っていました。特に、SUTFactoryの容量でAutoFixtureを使用することに興味があります。

AutoMockingに関するMarkSeemannのブログ投稿をいくつか用意して、そこから作業を試みましたが、それほど遠くはありませんでした。

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

テストを完全にグリーンにします。この行は、インスタンス化するときにAutoFixtureが内部で実行することを期待していたものとまったく同じSettingMappingXmlです。

次に、コンポーネントをフリーズするという概念があるので、モックオブジェクトを取得するのではなく、フィクスチャ内のモックをフリーズするだけで済みます。

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

確かに、これは完全に正常に機能します-SettingMappingXmlコンストラクターを明示的に呼び出し、に依存しない限りCreateAnonymous()



簡単に言えば、それが私が思いつくことができるどんな論理にも反するので、なぜそれが明らかにそれがするように働くのか理解していません。通常、私はライブラリのバグを疑うでしょうが、これは非常に基本的なものであるため、他の人がこれに遭遇し、長い間発見されて修正されていたと確信しています。さらに、テストとDIに対するMarkの熱心なアプローチを知っているので、これは意図的ではありません。

つまり、私はかなり初歩的な何かを見逃しているに違いありません。事前設定されたモックオブジェクトを依存関係として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を「クリーンな」プロジェクトにインストールしてMoq3.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単なる自動プロパティです。

4

2 に答える 2

15

SettingKeyプロパティが次のように定義されていると仮定すると、問題を再現できます。

public string SettingKey { get; set; }

何が起こるかというと、SettingMappingXmlインスタンスに注入されたテストダブル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の重要なポイントの1つです。単体テストの問題は、多くの場合、単体テスト(またはAutoFixture)自体の障害ではなく、設計上の欠陥に関する症状です。

この場合、それはデザインが十分にフールプルーフではないことを私に示しています。クライアントが自由に操作できることは本当に適切SettingKeyですか?

最低限、次のような代替実装をお勧めします。

public string SettingKey { get; private set; }

その変更で、私の再現は合格します。

SettingKeyを省略します

デザインを変更できない(または変更しない)場合は、SettingKeyプロパティの設定をスキップするようにAutoFixtureに指示できます。

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

チェーンに2つまたは3つを超えるカスタマイズを取得すると、そのメソッドチェーンを常に作成することに飽きてしまう可能性があります。これらのカスタマイズを特定のライブラリの一連の規則にカプセル化するときが来ました。

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と呼ばれるショートカットがあります。

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;
于 2012-11-22T09:22:22.133 に答える
4

最初のテストでは、以下を適用してFixtureクラスのインスタンスを作成できます。AutoMoqCustomization

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 1ですStep 2

に依存するタイプStep 3のインスタンスをリクエストします。Auto Mockingを使用しているため、クラスはこれらのインターフェースにモックを提供します。ただし、以前にそれらを注入したため、作成済みのモックが自動的に提供されるようになりました。SettingMappingXmlISettingsITracingServiceFixtureFreeze

于 2012-11-21T02:50:43.433 に答える