shim を使用して単体テストでアプリケーションを他のアセンブリから分離する

Shim 型 は 環境のテスト対象のコンポーネントを分離することが簡単に許可する Microsoft Fakes フレームワーク使用 2 種類の 1 つです。シムは、特定のメソッドにテストの一部として作成するコードに呼び出しを変更します。外部条件に依存する多くのメソッドのサイズの異なる結果が shim はテストの管理下にあり、呼び出しで一貫性のある結果を返すことができます。これにより、テストをより簡単に作成できます。します。

ソリューションの一部ではないアセンブリからコードを分離するために shim を使用します。一方のソリューションのコンポーネントを分離するために、スタブを使用することをお勧めします。

概要とクイック スタートのガイダンスについては、Microsoft Fakes を使用したテストでのコードの分離を参照してください。

要件

  • Visual Studio Ultimate

参照してください。ビデオ (1h16) : Visual Studio 2012 で Fakes "テストの非検証可能なコード

このトピックの内容

次にこのトピックの学習:

例: Y2K バグ

Shim の使用方法

  • Fakes アセンブリを追加する

  • ShimsContext を使用する

  • shim を使用してテストを作成する

さまざまな種類のメソッドの shim

既定の動作を変更する

環境アクセスの検出

Concurrency

shim のメソッドから元のメソッドを呼び出します。

制限事項

例: Y2K バグ

2000 年の 1 年 1 月 1 日の例外をスローするメソッドを検討する:

// code under test
public static class Y2KChecker {
    public static void Check() {
        if (DateTime.Now == new DateTime(2000, 1, 1))
            throw new ApplicationException("y2kbug!");
    }
}

このメソッドをテストする場合は、プログラムが DateTime.Nowに依存するため、特に問題となるのは、コンピューターのクロックに依存するメソッド、環境に依存し、非確定的なメソッド。さらに、DateTime.Now は静的プロパティです。したがって、スタブ型はここで使用できません。この問題は、単体テストの分離問題につながっています: データベース API を直接呼び出すプログラムは Web サービスと、ロジックが環境によって異なるため、単体テストなどに困難です通信します。

これは shim の型を使用する必要がある。shim の型はユーザー定義のなデリゲートを迂回する機能、.NET のメソッドを提供します。shim の型は Fakes のジェネレーターによってコードが生成され、これが shim の型と新しいクエリ メソッドの実装を指定するには、デリゲートを使用します。

次のテストは DateTime.Now のカスタム実装を提供するのに対し、ShimDateTimeの型を使用する方法について説明します。:

//unit test code
// create a ShimsContext cleans up shims 
using (ShimsContext.Create()
    // hook delegate to the shim method to redirect DateTime.Now
    // to return January 1st of 2000
    ShimDateTime.NowGet = () => new DateTime(2000, 1, 1);
    Y2KChecker.Check();
}

Shim の使用方法

Fakes アセンブリを追加する

  1. ソリューション エクスプローラーで、単体テスト プロジェクトの [参照] を展開します。

    • Visual Basic では、ソリューション エクスプローラーのツール バーの参照一覧を参照するに [すべてのファイルを表示] を選択します必要があります。
  2. 、shim を作成するクラス定義を含むアセンブリを選択します。たとえば、DateTime に Shim が足りない場合は、System.dll

  3. ショートカット メニューで、[Fakes アセンブリに追加(F)] をクリックします。

ShimsContext を使用する

shim を使用すると、単体テスト フレームワーク ShimsContext のテスト コードを shim の有効期間を制御するためにラップしなければ必要です。私たちはこれを必要としない場合は、シムは AppDomain がシャットダウンするまで保持されます。ShimsContext を作成する最も簡単な方法は、次のコードに示すように Create() の静的なメソッドを使用して実行:

//unit test code
[Test]
public void Y2kCheckerTest() {
  using(ShimsContext.Create()) {
    ...
  } // clear all shims
}

適切に各 shim のコンテキストを破棄することが重要です。一般に、登録されている shim の適切な消去を確認するに using のステートメント内の ShimsContext.Create を常に呼び出します。たとえば、2000 年 7 月 1 日 1 番目の元に戻すデリゲートと DateTime.Now のメソッドを置き換えるテスト メソッドの shim を登録する場合があります。テスト メソッドの登録済み shim を消去することを忘れるとテスト実行の他の部分は 1 1 月 1 2000 日から DateTime.Now 値に返します。これは意外、複雑になる場合があります。

shim を使用してテストを作成する

テスト コードで評価するメソッドの 迂回を 挿入します。たとえば、次のようになります。

[TestClass]
public class TestClass1
{ 
        [TestMethod]
        public void TestCurrentYear()
        {
            int fixedYear = 2000;

            using (ShimsContext.Create())
            {
              // Arrange:
                // Detour DateTime.Now to return a fixed date:
                System.Fakes.ShimDateTime.NowGet = 
                () =>
                { return new DateTime(fixedYear, 1, 1); };

                // Instantiate the component under test:
                var componentUnderTest = new MyComponent();

              // Act:
                int year = componentUnderTest.GetTheCurrentYear();

              // Assert: 
                // This will always be true if the component is working:
                Assert.AreEqual(fixedYear, year);
            }
        }
}
<TestClass()> _
Public Class TestClass1
    <TestMethod()> _
    Public Sub TestCurrentYear()
        Using s = Microsoft.QualityTools.Testing.Fakes.ShimsContext.Create()
            Dim fixedYear As Integer = 2000
            ' Arrange:
            ' Detour DateTime.Now to return a fixed date:
            System.Fakes.ShimDateTime.NowGet = _
                Function() As DateTime
                    Return New DateTime(fixedYear, 1, 1)
                End Function

            ' Instantiate the component under test:
            Dim componentUnderTest = New MyComponent()
            ' Act:
            Dim year As Integer = componentUnderTest.GetTheCurrentYear
            ' Assert: 
            ' This will always be true if the component is working:
            Assert.AreEqual(fixedYear, year)
        End Using
    End Sub
End Class

Shim クラスの名前は、元の型名の先頭に Fakes.Shim を付けることで構成されます。

シムは、テスト対象のアプリケーションのコードに 迂回を 挿入することで機能します。常に元のメソッドへの呼び出しは、実際のメソッドを呼び出す代わりに、shim コードが呼び出されるように、システムが迂回を実行 Fakes 発生します。

迂回が実行時に作成および削除されることに注意してください。ShimsContextの有効期間内の迂回を作成する必要があります。オブジェクトが破棄されると、アクティブな削除されますが、作成した shim。最も良い方法は using のステートメント内にあります。

Fakes 名前空間がないことを示すビルド エラーを参照する場合があります。このエラーは、ほかのコンパイル エラーが発生した場合に表示されます。他のエラーを解決することを削除します。

さまざまな種類のメソッドの shim

shim の型は独自のデリゲートと .NET のメソッドは、静的メソッドまたは非仮想メソッドが、置き換えることができます。

静的メソッド

静的メソッドに shim を接続するプロパティは shim の型に配置されます。各プロパティにターゲット メソッドにデリゲートを接続するために使用できる構成のみです。たとえば、クラスの静的メソッド MyMethodの MyClass を指定する:

//code under test
public static class MyClass {
    public static int MyMethod() {
        ...
    }
}

これは、MyMethod に常に返す 5 を shim を接続できます。:

// unit test code
ShimMyClass.MyMethod = () =>5;

インスタンス メソッド (すべてのインスタンス用)

同様に、静的メソッド、インスタンス メソッド、すべてのインスタンスに対して Shim を送信できます。これらの shim を接続するプロパティは AllInstances という入れ子にされた型に混乱を避けるために配置されます。たとえば、クラスのインスタンス メソッド MyMethodの MyClass を指定する:

// code under test
public class MyClass {
    public int MyMethod() {
        ...
    }
}

MyMethod にインスタンスに関係なく 5 を常に返す shim を接続する:

// unit test code
ShimMyClass.AllInstances.MyMethod = () => 5;

ShimMyClass の生成された型の構造体には次のコードのようになります。:

// Fakes generated code
public class ShimMyClass : ShimBase<MyClass> {
    public static class AllInstances {
        public static Func<MyClass, int>MyMethod {
            set {
                ...
            }
        }
    }
}

Fakes がデリゲートの最初の引数としてランタイム インスタンス (この場合は渡すことに注意してください。

インスタンス メソッド (1 つの実行時インスタンス用)

インスタンス メソッドが、呼び出しの受信側に基づいて異なるデリゲートによって、Shim を送信できます。これは、同じインスタンス メソッドは型のインスタンスごとに異なる動作をすることができます。これらの shim を設定するプロパティは、shim 型のインスタンス メソッド自体です。それぞれのインスタンス化された shim の型も Shim を埋め込んだ型の生インスタンスに関連付けられます。

たとえば、クラスのインスタンス メソッド MyMethodの MyClass を指定する:

// code under test
public class MyClass {
    public int MyMethod() {
        ...
    }
}

まず、の 1 つが 5 を常に戻り、2 番目は、常に 10 を返すように MyMethod の 2 種類の shim の型を設定できます。:

// unit test code
var myClass1 = new ShimMyClass()
{
    MyMethod = () => 5
};
var myClass2 = new ShimMyClass { MyMethod = () => 10 };

ShimMyClass の生成された型の構造体には次のコードのようになります。:

// Fakes generated code
public class ShimMyClass : ShimBase<MyClass> {
    public Func<int> MyMethod {
        set {
            ...
        }
    }
    public MyClass Instance {
        get {
            ...
        }
    }
}

実際の Shim を埋め込んだ型のインスタンスがインスタンス プロパティを使用してアクセスできます。:

// unit test code
var shim = new ShimMyClass();
var instance = shim.Instance;

shim の型にも Shim を埋め込んだ型への暗黙の型変換が存在するため、次のようになるように通常 shim の型を使用する:

// unit test code
var shim = new ShimMyClass();
MyClass instance = shim; // implicit cast retrieves the runtime
                         // instance

コンストラクター

コンストラクターは、将来のに対し、オブジェクトの型を接続する Shim を送信できます。各コンストラクターは、shim 型の静的コンストラクター メソッドで公開されます。たとえば、クラスに整数を取得するコンストラクターで MyClass を指定する:

// code under test
public class MyClass {
    public MyClass(int value) {
        this.Value = value;
    }
    ...
}

これは、値の取得が呼び出されると、将来のインスタンスがコンストラクターの値に関係なく、-5 を返すようにコンストラクターの shim の型を設定する:

// unit test code
ShimMyClass.ConstructorInt32 = (@this, value) => {
    var shim = new ShimMyClass(@this) {
        ValueGet = () => -5
    };
};

shim の各型に 2 人のコンストラクターを公開していることに注意してください。既定のコンストラクターは、引数として Shim を埋め込んだインスタンスを取得するコンストラクターは、コンストラクターの shim を使用する必要はありませんが、新しいインスタンスが必要な場合に使用する必要があります:

// unit test code
public ShimMyClass() { }
public ShimMyClass(MyClass instance) : base(instance) { }

ShimMyClass に生成された型の構造は followoing コードに似ています:

// Fakes generated code
public class ShimMyClass : ShimBase<MyClass>
{
    public static Action<MyClass, int> ConstructorInt32 {
        set {
            ...
        }
    }

    public ShimMyClass() { }
    public ShimMyClass(MyClass instance) : base(instance) { }
    ...
}

基本メンバー

基本メンバーの shim のプロパティは、基本型の shim を作成し、基本 shim クラスのコンストラクターにパラメーターとして子インスタンスを渡すことによってアクセスできます。

たとえば、クラスのインスタンス メソッド MyMethod およびサブタイプ MyChildの MyBase を指定する:

public abstract class MyBase {
    public int MyMethod() {
        ...
    }
}

public class MyChild : MyBase {
}

これは ShimMyBase の新しい shim を作成して MyBase の shim をセットアップしています:

// unit test code
var child = new ShimMyChild();
new ShimMyBase(child) { MyMethod = () => 5 };

基本 shim のコンストラクターにパラメーターとして渡される場合、子インスタンスに変換される子の shim の型が暗黙的であることに注意してください。

ShimMyChild と ShimMyBase の生成された型の構造体には次のコードに似ています:

// Fakes generated code
public class ShimMyChild : ShimBase<MyChild> {
    public ShimMyChild() { }
    public ShimMyChild(Child child)
        : base(child) { }
}
public class ShimMyBase : ShimBase<MyBase> {
    public ShimMyBase(Base target) { }
    public Func<int> MyMethod
    { set { ... } }
}

静的コンストラクター

shim の型の静的コンストラクターに Shim を送信するための静的メソッド StaticConstructor を公開します。静的コンストラクターは実行されないため、一度型のメンバーにアクセスする前にのみ shim が構成されていることを確認する必要があります。

ファイナライザー

ファイナライザーは Fakes ではサポートされていません。

プライベート メソッド

Fakes のコード ジェネレーターに表示が型シグネチャ、つまり参照パラメーターの型と戻り値の型を指定できるプライベート メソッドの shim のプロパティを作成します。

バインド インターフェイス

Shim を埋め込んだ型がインターフェイスを実装すると、コード ジェネレーターは、そのインターフェイスのメンバーをすばやくバインドするこのメソッドを生成します。

たとえば、IEnumerable<int>を実装するクラスに MyClass を指定する:

public class MyClass : IEnumerable<int> {
    public IEnumerator<int> GetEnumerator() {
        ...
    }
    ...
}

これは、バインドのメソッドを呼び出して、MyClass の IEnumerable<int> の実装に Shim を送信できないです:

// unit test code
var shimMyClass = new ShimMyClass();
shimMyClass.Bind(new List<int> { 1, 2, 3 });

ShimMyClass の生成された型の構造体には次のコードに似ています:

// Fakes generated code
public class ShimMyClass : ShimBase<MyClass> {
    public ShimMyClass Bind(IEnumerable<int> target) {
        ...
    }
}

既定の動作を変更する

生成された各 shim の型は ShimBase<T>.InstanceBehavior のプロパティを通じて IShimBehavior インターフェイス インスタンスを保持します。動作はたびにクライアントの呼び出し明示的に Shim を送信されていないインスタンス メンバーが使用されます。

動作が明示的に設定されていない場合、ShimsBehaviors.Current の静的なプロパティによって返されるインスタンスを使用します。既定では、このプロパティ NotImplementedException の例外をスローする動作を返します。

この動作は、shim のインスタンスの InstanceBehavior のプロパティを設定することにより、いつでも変更できます。たとえば、次のスニペットでは何も行われずに戻り、既定の既定値を返さない動作にある型と shim を変更します (T) :

// unit test code
var shim = new ShimMyClass();
//return default(T) or do nothing
shim.InstanceBehavior = ShimsBehaviors.DefaultValue;

実行は、InstanceBehavior のプロパティが ShimsBehaviors.Current の静的なプロパティの設定によって明示的に設定されていないすべての Shim を埋め込んだインスタンスでグローバルに変更できます:

// unit test code
// change default shim for all shim instances
// where the behavior has not been set
ShimsBehaviors.Current = 
    ShimsBehaviors.DefaultValue;

環境アクセスの検出

対応する shim 型の静的プロパティ Behavior に ShimsBehaviors.NotImplemented の動作を割り当てることによってすべてのメンバーの動作を、特定の型の静的メソッドが、アタッチした可能性があります:

// unit test code
// assigning the not implemented behavior
ShimMyClass.Behavior = ShimsBehaviors.NotImplemented;
// shorthand
ShimMyClass.BehaveAsNotImplemented();

同時実行

shim の型は AppDomain 内のすべてのスレッドに適用され、スレッド アフィニティはありません。これは、テスト ランナーを使用しようとしている重要なこと、同時実行をサポートする: shim の型を含むテストを同時に実行できます。このプロパティは、ランタイムによって enfored Fakes です。

shim のメソッドから元のメソッドを呼び出します。

これを実際にメソッドに渡されたファイル名を検証した後で、システム ファイルにテキストを書き込む必要があるとします。その場合は、shim のメソッドの中で元のメソッドを呼び出す必要があります。

この問題を解決する一つの方法は、次のコードのように、デリゲートと ShimsContext.ExecuteWithoutShims() を使用して元のメソッド呼び出しをラップすることがあります:

// unit test code
ShimFile.WriteAllTextStringString = (fileName, content) => {
  ShimsContext.ExecuteWithoutShims(() => {

      Console.WriteLine("enter");
      File.WriteAllText(fileName, content);
      Console.WriteLine("leave");
  });
};

もう一つの方法は無効にし、元のメソッドを呼び出し、shim を復元する shim を設定します。

// unit test code
ShimsDelegates.Action<string, string> shim = null;
shim = (fileName, content) => {
  try {
    Console.WriteLine("enter”);
    // remove shim in order to call original method
    ShimFile.WriteAllTextStringString = null;
    File.WriteAllText(fileName, content);
  }
  finally
  {
    // restore shim
    ShimFile.WriteAllTextStringString = shim;
    Console.WriteLine("leave");
  }
};
// initialize the shim
ShimFile.WriteAllTextStringString = shim;

制限事項

シムは .NET の基本クラス ライブラリ mscorlibシステムからすべての型で使用することはできません。

外部リソース

ガイダンス

Testing for Continuous Delivery with Visual Studio 2012 – Chapter 2: Unit Testing: Testing the Inside (Visual Studio 2012 を使用した絶え間のない配信のためのテスト – 第 2 章: 単体テスト: 内部のテスト)

参照

概念

Microsoft Fakes を使用したテストでのコードの分離

その他の技術情報

Peter に処理寮長のブログ: Visual Studio 2012 の shim

ビデオ (1h16) : Visual Studio 2012 で Fakes "テストの非検証可能なコード