スタブを使用して単体テストでアプリケーションの各部分を相互に分離するUsing stubs to isolate parts of your application from each other for unit testing

スタブ型は、テスト対象のコンポーネントをそれが呼び出した他のコンポーネントから簡単に区別できるようにするために Microsoft Fakes フレームワークによって提供されている 2 つのテクノロジのうちの 1 つです。Stub types are one of two technologies that the Microsoft Fakes framework provides to let you easily isolate a component you are testing from other components that it calls. スタブは、テスト中に別のコンポーネントの代わりをする短いコードです。A stub is a small piece of code that takes the place of another component during testing. スタブを使用することの利点は、スタブによって一貫した結果が返され、テストを簡単に記述できることです。The benefit of using a stub is that it returns consistent results, making the test easier to write. また、他のコンポーネントがまだ動作しなくてもテストを実行できます。And you can run tests even if the other components are not working yet.

Fakes の概要とクイック スタート ガイドについては、「Microsoft Fakes を使用したテストでのコードの分離」を参照してください。For an overview and quick start guide to Fakes, see Isolating Code Under Test with Microsoft Fakes.

スタブを使用するには、アプリケーションの他の部分を参照する際に、クラスでなくインターフェイスだけを使用するようにコンポーネントを記述する必要があります。To use stubs, you have to write your component so that it uses only interfaces, not classes, to refer to other parts of the application. 1 つの部分で変更があったときに別の部分でも変更が必要になる可能性が低くなるため、これは優れたデザイン方法です。This is a good design practice because it makes changes in one part less likely to require changes in another. テストでは、実際のコンポーネントをスタブに置き換えることができます。For testing, it allows you to substitute a stub for a real component.

図では、StockAnalyzer というコンポーネントがテスト対象です。In the diagram, the component StockAnalyzer is the one we want to test. これは通常、別のコンポーネントである RealStockFeed を使用します。It normally uses another component, RealStockFeed. しかし、RealStockFeed はメソッドが呼び出されるたびに異なる結果を返すので、StockAnalyzer のテストが難しくなります。But RealStockFeed returns different results every time its methods are called, making it difficult to test StockAnalyzer. そこで、テスト中は StubStockFeed という別のクラスに置き換えます。During testing, we replace it with a different class, StubStockFeed.

Real クラスと Stub クラスは 1 つのインターフェイスに準拠しています。Real and Stub classes conform to one interface.

スタブは、コードをこのように構成できることに依存しているため、通常は自分が管理しているアプリケーションの 1 つの部分を他の部分から分離するために使用されます。Because stubs rely on your being able to structure your code in this way, you typically use stubs to isolate one part of your application from another. 自分の管理下にない他のアセンブリ (System.dll など) から分離するには、通常は shim を使用します。To isolate it from other assemblies that are not under your control, such as System.dll, you would normally use shims. shim を使用して単体テストでアプリケーションを他のアセンブリから分離する」を参照してください。See Using shims to isolate your application from other assemblies for unit testing.

RequirementsRequirements

  • Visual Studio EnterpriseVisual Studio Enterprise

このトピックの内容In this topic

スタブの使用方法How to use stubs

依存関係の挿入のデザインDesign for dependency injection

スタブを使用するには、各コンポーネントが互いに依存せず、インターフェイス定義だけに依存するようにアプリケーションをデザインする必要があります。To use stubs, your application has to be designed so that the different components are not dependent on each other, but only dependent on interface definitions. コンポーネントはコンパイル時には結合されず、実行時に接続されます。Instead of being coupled at compile time, components are connected at run time. このパターンでは、変更がコンポーネントの境界を越えて広がることが少ないので、堅牢で更新しやすいソフトウェアを作成できます。This pattern helps to make software that is robust and easy to update, because changes tend not to propagate across component boundaries. スタブを使用しない場合でも、このパターンに従うことをお勧めします。We recommend following it even if you don't use stubs. 新しいコードを記述する場合は、依存関係の挿入パターンに簡単に準拠できます。If you are writing new code, it's easy to follow the dependency injection pattern. 既存のソフトウェアのテストを作成する場合は、通常、ソフトウェアをリファクタリングする必要があります。If you are writing tests for existing software, you might have to refactor it. それが現実的でない場合は、代わりに shim を使用する方法もあります。If that would be impractical, you could consider using shims instead.

ここではまず、図に示されているような、スタブに適した例について説明します。Let's start this discussion with a motivating example, the one in the diagram. クラス StockAnalyzer は、株価を読み取り、興味深い結果を生成します。The class StockAnalyzer reads share prices and generates some interesting results. いくつかのパブリック メソッドがあり、それらをテスト対象にします。It has some public methods, which we want to test. 説明を簡単にするために、これらのメソッドのうちの 1 つに着目します。特定の株式の現在の価格を報告する単純なメソッドです。To keep things simple, let's just look at one of those methods, a very simple one that reports the current price of a particular share. このメソッドの単体テストを記述します。We want to write a unit test of that method. テストの最初のドラフトは、次のようになります。Here's the first draft of a test:

[TestMethod]  
public void TestMethod1()  
{  
    // Arrange:  
    var analyzer = new StockAnalyzer();  
    // Act:  
    var result = analyzer.GetContosoPrice();  
    // Assert:  
    Assert.AreEqual(123, result); // Why 123?  
}  
<TestMethod()> Public Sub TestMethod1()  
    ' Arrange:  
    Dim analyzer = New StockAnalyzer()  
    ' Act:  
    Dim result = analyzer.GetContosoPrice()  
    ' Assert:  
    Assert.AreEqual(123, result) ' Why 123?  
End Sub  

このテストの問題は、一見して明らかです。株価は変化するので、アサーションは通常は失敗します。One problem with this test is immediately obvious: share prices vary, and so the assertion will usually fail.

もう 1 つの問題は、StockAnalyzer によって使用される StockFeed コンポーネントがまだ開発中であることです。Another problem might be that the StockFeed component, which is used by the StockAnalyzer, is still under development. テスト中のメソッドのコードは、最初のドラフトで、次のようになります。Here's the first draft of the code of the method under test:

public int GetContosoPrice()  
{  
    var stockFeed = new StockFeed(); // NOT RECOMMENDED  
    return stockFeed.GetSharePrice("COOO");  
}  
Public Function GetContosoPrice()  
    Dim stockFeed = New StockFeed() ' NOT RECOMMENDED  
    Return stockFeed.GetSharePrice("COOO")  
End Function  

現時点では、StockFeed クラスの作業が完了していないため、このメソッドはコンパイルされないか、例外をスローする場合があります。As it stands, this method might not compile or might throw an exception because work on the StockFeed class is not yet complete.

インターフェイス挿入は、両方の問題を解決します。Interface injection addresses both of these problems.

インターフェイス挿入では、次の規則が適用されます。Interface injection applies the following rule:

  • アプリケーションのすべてのコンポーネントのコードは、宣言内でも、new ステートメント内でも、他のコンポーネントのクラスを明示的に参照してはいけません。The code of any component of your application should never explicitly refer to a class in another component, either in a declaration or in a new statement. 代わりに、変数とパラメーターは、インターフェイス付きで宣言する必要があります。Instead, variables and parameters should be declared with interfaces. コンポーネントのインスタンスは、コンポーネントのコンテナーのみで作成されます。Component instances should be created only by the component's container.

    ここでは、"コンポーネント" はクラス、または一緒に開発および更新するクラスのグループを意味します。By "component" in this case we mean a class, or a group of classes that you develop and update together. 通常、コンポーネントは 1 つの Visual Studio プロジェクト内のコードです。Typically, a component is the code in one Visual Studio project. 1 つのコンポーネント内の各クラスは同時に更新されるため、それらを分離することは、あまり重要ではありません。It's less important to decouple classes within one component, because they are updated at the same time.

    System.dll などの比較的安定したプラットフォームのクラスからコンポーネントを分離することも、あまり重要ではありません。It is also not so important to decouple your components from the classes of a relatively stable platform such as System.dll. これらのすべてのクラスのインターフェイスを記述したのでは、コードが煩雑になってしまいます。Writing interfaces for all these classes would clutter your code.

    StockAnalyzer のコードは、次のようにインターフェイスを使用して StockFeed から分離することによって、より良いコードにすることができます。The StockAnalyzer code can therefore be improved by decoupling it from the StockFeed by using an interface like this:

public interface IStockFeed  
{  
    int GetSharePrice(string company);  
}  

public class StockAnalyzer  
{  
    private IStockFeed stockFeed;  
    public Analyzer(IStockFeed feed)  
    {  
        stockFeed = feed;  
    }  
    public int GetContosoPrice()  
    {  
        return stockFeed.GetSharePrice("COOO");  
    }  
}  
Public Interface IStockFeed  
    Function GetSharePrice(company As String) As Integer  
End Interface  

Public Class StockAnalyzer  
    ' StockAnalyzer can be connected to any IStockFeed:  
    Private stockFeed As IStockFeed  
    Public Sub New(feed As IStockFeed)  
        stockFeed = feed  
    End Sub    
    Public Function GetContosoPrice()  
        Return stockFeed.GetSharePrice("COOO")  
    End Function  
End Class  

この例では、StockAnalyzer が構築されるときに IStockFeed の実装が渡されます。In this example, StockAnalyzer is passed an implementation of an IStockFeed when it is constructed. 完成したアプリケーションでは、次のように、初期化コードによって接続が行われます。In the completed application, the initialization code would perform the connection:

analyzer = new StockAnalyzer(new StockFeed())  

この接続を行うには、より柔軟な方法があります。There are more flexible ways of performing this connection. たとえば、StockAnalyzer は、さまざまな条件で IStockFeed のさまざまな実装をインスタンス化できるファクトリ オブジェクトを受け取ることができます。For example, StockAnalyzer could accept a factory object that can instantiate different implementations of IStockFeed in different conditions.

スタブを生成するGenerate stubs

テスト対象のクラスを、それが使用する他のコンポーネントから分離しました。You've decoupled the class you want to test from the other components that it uses. 分離することによって、アプリケーションの堅牢性と柔軟性を高めるだけでなく、テスト対象のコンポーネントをインターフェイスのスタブ実装にテスト用に接続することもできます。As well as making the application more robust and flexible, the decoupling allows you to connect the component under test to stub implementations of the interfaces for test purposes.

スタブは、単に通常の方法でクラスとして記述することもできます。You could simply write the stubs as classes in the usual way. しかし、Microsoft Fakes では、各テストで最も適切なスタブを作成するための、より動的な手段が用意されています。But Microsoft Fakes provides you with a more dynamic way to create the most appropriate stub for every test.

スタブを使用するには、まず、インターフェイス定義からスタブ型を生成する必要があります。To use stubs, you must first generate stub types from the interface definitions.

Fakes アセンブリを追加するAdding a Fakes Assembly
  1. ソリューション エクスプローラーで、単体テスト プロジェクトの [参照設定] を展開します。In Solution Explorer, expand your unit test project's References.

    • Visual Basic で作業している場合、参照一覧を表示するには、ソリューション エクスプローラー ツール バーの [すべてのファイルを表示] を選択する必要があります。If you are working in Visual Basic, you must select Show All Files in the Solution Explorer toolbar, in order to see the References list.
  2. 作成するスタブに対応するインターフェイス定義が含まれているアセンブリを選択します。Select the assembly that contains the interface definitions for which you want to create stubs.

  3. ショートカット メニューで、[Fakes アセンブリに追加] を選択します。On the shortcut menu, choose Add Fakes Assembly.

スタブを使用してテストを作成するWrite your test with stubs

[TestClass]  
class TestStockAnalyzer  
{  
    [TestMethod]  
    public void TestContosoStockPrice()  
    {  
      // Arrange:  

        // Create the fake stockFeed:  
        IStockFeed stockFeed =   
             new StockAnalysis.Fakes.StubIStockFeed() // Generated by Fakes.  
                 {  
                     // Define each method:  
                     // Name is original name + parameter types:  
                     GetSharePriceString = (company) => { return 1234; }  
                 };  

        // In the completed application, stockFeed would be a real one:  
        var componentUnderTest = new StockAnalyzer(stockFeed);  

      // Act:  
        int actualValue = componentUnderTest.GetContosoPrice();  

      // Assert:  
        Assert.AreEqual(1234, actualValue);  
    }  
    ...  
}  
<TestClass()> _  
Class TestStockAnalyzer  

    <TestMethod()> _  
    Public Sub TestContosoStockPrice()  
        ' Arrange:  
        ' Create the fake stockFeed:  
        Dim stockFeed As New StockAnalysis.Fakes.StubIStockFeed  
        With stockFeed  
            .GetSharePriceString = Function(company)  
                                       Return 1234  
                                   End Function  
        End With  
        ' In the completed application, stockFeed would be a real one:  
        Dim componentUnderTest As New StockAnalyzer(stockFeed)  
        ' Act:  
        Dim actualValue As Integer = componentUnderTest.GetContosoPrice  
        ' Assert:  
        Assert.AreEqual(1234, actualValue)  
    End Sub  
End Class  

ここでの特殊なマジックは、StubIStockFeed クラスです。The special piece of magic here is the class StubIStockFeed. 参照アセンブリのそれぞれのパブリック型に対して、Microsoft Fakes のメカニズムによってスタブ クラスが生成されます。For every public type in the referenced assembly, the Microsoft Fakes mechanism generates a stub class. スタブ クラスの名前はインターフェイスの名前から派生します。プレフィックスとして Fakes.Stub が付き、パラメーターの型名が加わります。The name of the stub class is the derived from the name of the interface, with "Fakes.Stub" as a prefix, and the parameter type names appended.

また、イベントおよびジェネリック メソッドについて、プロパティの getter および setter に対してもスタブが生成されます。Stubs are also generated for the getters and setters of properties, for events, and for generic methods.

パラメーター値を確認するVerifying parameter values

自分のコンポーネントが他のコンポーネントを呼び出すときに、適切な値が渡されることを検証できます。You can verify that when your component makes a call to another component, it passes the correct values. スタブ内にアサーションを配置するか、値を保存して、テストの本体で検証できます。You can either place an assertion in the stub, or you can store the value and verify it in the main body of the test. 例:For example:

[TestClass]  
class TestMyComponent  
{  

    [TestMethod]  
    public void TestVariableContosoPrice()  
    {  
     // Arrange:  
        int priceToReturn;  
        string companyCodeUsed;  
        var componentUnderTest = new StockAnalyzer(new StubIStockFeed()  
            {  
               GetSharePriceString = (company) =>   
                  {   
                     // Store the parameter value:  
                     companyCodeUsed = company;  
                     // Return the value prescribed by this test:  
                     return priceToReturn;  
                  };  
            };  
        // Set the value that will be returned by the stub:  
        priceToReturn = 345;  

     // Act:  
        int actualResult = componentUnderTest.GetContosoPrice();  

     // Assert:  
        // Verify the correct result in the usual way:  
        Assert.AreEqual(priceToReturn, actualResult);  

        // Verify that the component made the correct call:  
        Assert.AreEqual("COOO", companyCodeUsed);  
    }  
...}  
<TestClass()> _  
Class TestMyComponent  
    <TestMethod()> _  
    Public Sub TestVariableContosoPrice()  
        ' Arrange:  
        Dim priceToReturn As Integer  
        Dim companyCodeUsed As String = ""  
        Dim stockFeed As New StockAnalysis.Fakes.StubIStockFeed()  
        With stockFeed  
            ' Implement the interface's method:  
            .GetSharePriceString = _  
                Function(company)  
                    ' Store the parameter value:  
                    companyCodeUsed = company  
                    ' Return a fixed result:  
                    Return priceToReturn  
                End Function  
        End With  
        ' Create an object to test:  
        Dim componentUnderTest As New StockAnalyzer(stockFeed)  
        ' Set the value that will be returned by the stub:  
        priceToReturn = 345  

        ' Act:  
        Dim actualResult As Integer = componentUnderTest.GetContosoPrice()  

        ' Assert:  
        ' Verify the correct result in the usual way:  
        Assert.AreEqual(priceToReturn, actualResult)  
        ' Verify that the component made the correct call:  
        Assert.AreEqual("COOO", companyCodeUsed)  
    End Sub  
...  
End Class  

さまざまな種類の型メンバーのスタブStubs for different kinds of type members

メソッドMethods

例に示すように、スタブ クラスのインスタンスにデリゲートをアタッチすることによって、メソッドをスタブすることができます。As described in the example, methods can be stubbed by attaching a delegate to an instance of the stub class. スタブ型の名前は、メソッドとパラメーターの名前から派生されます。The name of the stub type is derived from the names of the method and parameters. たとえば、IMyInterface というインターフェイスと MyMethod というメソッドがあるとします。For example, given the following IMyInterface interface and method MyMethod:

// application under test  
interface IMyInterface   
{  
    int MyMethod(string value);  
}  

スタブを、常に 1 を返す MyMethod にアタッチします。We attach a stub to MyMethod that always returns 1:

// unit test code  
  var stub = new StubIMyInterface ();  
  stub.MyMethodString = (value) => 1;  

関数のスタブを指定しないと、戻り値の型の既定値を返す関数が Fakes によって生成されます。If you do not provide a stub for a function, Fakes will generate a function that returns the default value of the return type. 数値の場合、既定値は 0 です。クラス型の場合は、null (C#) または Nothing (Visual Basic) です。For numbers, the default value is 0, and for class types it is null (C#) or Nothing (Visual Basic).

プロパティProperties

プロパティの getter と setter は、個別のデリゲートとして公開され、個別にスタブできます。Property getters and setters are exposed as separate delegates and can be stubbed separately. 例として、ValueIMyInterface プロパティを考えます。For example, consider the Value property of IMyInterface:

// code under test  
interface IMyInterface   
{  
    int Value { get; set; }  
}  

自動プロパティをシミュレートするために、Value の getter と setter にデリゲートをアタッチします。We attach delegates to the getter and setter of Value to simulate an auto-property:

// unit test code  
int i = 5;  
var stub = new StubIMyInterface();  
stub.ValueGet = () => i;  
stub.ValueSet = (value) => i = value;  

プロパティの setter または getter にスタブ メソッドを指定しないと、値を格納するスタブが Fakes によって生成されます。そのため、スタブ プロパティは単純な変数のように動作します。If you do not provide stub methods for either the setter or the getter of a property, Fakes will generate a stub that stores values, so that the stub property works like a simple variable.

イベントEvents

イベントは、デリゲート フィールドとして公開されます。Events are exposed as delegate fields. そのため、スタブされたイベントは、単にイベントのバッキング フィールドを呼び出すだけで発生させることができます。As a result, any stubbed event can be raised simply by invoking the event backing field. 次のようなインターフェイスをスタブするとします。Let's consider the following interface to stub:

// code under test  
interface IWithEvents   
{  
    event EventHandler Changed;  
}  

Changed イベントを発生させるには、単にバッキング デリゲートを呼び出します。To raise the Changed event, we simply invoke the backing delegate:

// unit test code  
  var withEvents = new StubIWithEvents();  
  // raising Changed  
  withEvents.ChangedEvent(withEvents, EventArgs.Empty);  

ジェネリック メソッドGeneric methods

メソッドに必要な各インスタンス化用のデリゲートを用意することによって、ジェネリック メソッドをスタブすることができます。It's possible to stub generic methods by providing a delegate for each desired instantiation of the method. たとえば、次のような、ジェネリック メソッドを含むインターフェイスがあるとします。For example, given the following interface containing a generic method:

// code under test  
interface IGenericMethod   
{  
    T GetValue<T>();  
}  

GetValue<int> のインスタンス化をスタブするテストを、次のように記述できます。you could write a test that stubs the GetValue<int> instantiation:

// unit test code  
[TestMethod]  
public void TestGetValue()   
{  
    var stub = new StubIGenericMethod();  
    stub.GetValueOf1<int>(() => 5);  

    IGenericMethod target = stub;  
    Assert.AreEqual(5, target.GetValue<int>());  
}  

コードが他のインスタンス化で GetValue<T> を呼び出す場合、スタブは単に動作を呼び出します。If the code were to call GetValue<T> with any other instantiation, the stub would simply call the behavior.

仮想クラスのスタブStubs of virtual classes

これまでの例では、スタブはインターフェイスから生成されていました。In the previous examples, the stubs have been generated from interfaces. 仮想メンバーまたは抽象メンバーを持つクラスからスタブを生成することもできます。You can also generate stubs from a class that has virtual or abstract members. 次に例を示します。For example:

// Base class in application under test  
    public abstract class MyClass  
    {  
        public abstract void DoAbstract(string x);  
        public virtual int DoVirtual(int n)  
        { return n + 42; }  
        public int DoConcrete()  
        { return 1; }  
    }  

このクラスから生成されたスタブでは、DoAbstract() と DoVirtual() のデリゲート メソッドを設定できますが、DoConcrete() のデリゲート メソッドは設定できません。In the stub generated from this class, you can set delegate methods for DoAbstract() and DoVirtual(), but not DoConcrete().

// unit test  
  var stub = new Fakes.MyClass();  
  stub.DoAbstractString = (x) => { Assert.IsTrue(x>0); };  
  stub.DoVirtualInt32 = (n) => 10 ;  

仮想メソッドのデリゲートを指定しない場合、Fakes は既定の動作を提供するか、基底クラスのメソッドを呼び出すことができます。If you do not provide a delegate for a virtual method, Fakes can either provide the default behavior, or it can call the method in the base class. 基本メソッドが呼び出されるようにするには、次のように CallBase プロパティを設定します。To have the base method called, set the CallBase property:

// unit test code  
var stub = new Fakes.MyClass();  
stub.CallBase = false;  
// No delegate set - default delegate:  
Assert.AreEqual(0, stub.DoVirtual(1));  

stub.CallBase = true;  
//No delegate set - calls the base:  
Assert.AreEqual(43,stub.DoVirtual(1));  

スタブをデバッグするDebugging stubs

スタブ型は、デバッグを円滑に行うことができるように設計されています。The stub types are designed to provide a smooth debugging experience. 既定では、デバッガーは生成されたすべてのコードをステップ オーバーするように設定されています。そのため、スタブにアタッチされたカスタム メンバー実装に直接ステップ インする必要があります。By default, the debugger is instructed to step over any generated code, so it should step directly into the custom member implementations that were attached to the stub.

スタブの制限事項Stub limitations

  1. ポインターを含むメソッド シグネチャはサポートされていません。Method signatures with pointers aren't supported.

  2. スタブ型は仮想メソッド ディスパッチに依存しているため、シール クラスまたは静的メソッドはスタブできません。Sealed classes or static methods can't be stubbed because stub types rely on virtual method dispatch. そのような場合、「shim を使用して単体テストでアプリケーションを他のアセンブリから分離する」の説明にある shim 型を利用してください。For such cases, use shim types as described in Using shims to isolate your application from other assemblies for unit testing

スタブの既定の動作を変更するChanging the default behavior of stubs

生成された各スタブ型は、IStubBehavior インターフェイスのインスタンスを保持します (IStub.InstanceBehavior プロパティを通じて)。Each generated stub type holds an instance of the IStubBehavior interface (through the IStub.InstanceBehavior property). この動作は、カスタム デリゲートをアタッチされていないメンバーをクライアントが呼び出すたびに呼び出されます。The behavior is called whenever a client calls a member with no attached custom delegate. 動作が設定されていない場合は、StubsBehaviors.Current プロパティによって返されるインスタンスが使用されます。If the behavior has not been set, it will use the instance returned by the StubsBehaviors.Current property. 既定では、このプロパティは NotImplementedException 例外をスローする動作を返します。By default, this property returns a behavior that throws a NotImplementedException exception.

動作は、任意のスタブ インスタンス上の InstanceBehavior プロパティを設定することによって、いつでも変更できます。The behavior can be changed at any time by setting the InstanceBehavior property on any stub instance. たとえば、次のスニペットは、何も行わないか、戻り値の型の既定値 (default(T)) を返す動作を変更します。For example, the following snippet changes a behavior that does nothing or returns the default value of the return type: default(T):

// unit test code  
var stub = new StubIFileSystem();  
// return default(T) or do nothing  
stub.InstanceBehavior = StubsBehaviors.DefaultValue;  

StubsBehaviors.Current プロパティを設定することによって、動作が設定されていないすべてのスタブ オブジェクトの動作をグローバルに変更することもできます。The behavior can also be changed globally for all stub objects for which the behavior has not been set by setting the StubsBehaviors.Current property:

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

外部リソースExternal resources

ガイダンスGuidance

Visual Studio 2012 を使用した継続的配信のためのテスト - 第 2 章: 単体テスト: 内部のテストTesting for Continuous Delivery with Visual Studio 2012 - Chapter 2: Unit Testing: Testing the Inside

関連項目See Also

Microsoft Fakes を使用したテストでのコードの分離Isolating Code Under Test with Microsoft Fakes