共用方式為


使用虛設常式隔離應用程式的各個組件,方便進行單元測試

將 Stub 型別 是 Microsoft Fakes 架構提供可讓您輕鬆地隔離元件您從其他元件測試它呼叫這兩項技術的其中一個。 Stub 是在測試期間,取代為另一個元件的一小段程式碼。 使用 Stub 的優點是它會傳回一致的結果,讓測試更容易撰寫。 您可以執行測試,即使其他元件無法運作。

如需 Fakes 的概觀和快速入門指南,請參閱 使用 Microsoft Fakes 在測試期間隔離程式碼

若要使用 Stub,您必須撰寫自己的元件,使其只使用介面,而不是類別,表示應用程式的其他部分。 因為有一部分認可變更不太可能需要在類別上的變更,這是一個好設計慣例。 如需測試,它可讓您用虛擬的元件替代 Stub。

在圖表上,元件 StockAnalyzer 是我們要測試的值。 它通常使用另一個元件, RealStockFeed。 但是, RealStockFeed 傳回不同的結果,在方法呼叫時,因此很難測試 StockAnalyzer。 在測試期間,我們以不同類別取代它, StubStockFeed。

實際和 Stub 類別都遵循單一介面。

因為 Stub 可以依賴這類建構您的程式碼,通常會使用 Stub 隔離在您從其他應用程式的一部分。 若要找出它從不在您的控制,例如 System.dll,您的其他組件通常會使用填充碼。 請參閱 使用填充碼將應用程式與其他組件隔離,方便進行單元測試

需求

  • Visual Studio Ultimate

本主題內容

如何使用 Stub

Hh549174.collapse_all(zh-tw,VS.110).gif相依性插入的設計

若要使用 Stub,您的應用程式必須被設計,讓不同的元件不彼此相依,不過,只有相依於介面定義。 而不會結合在編譯時期,元件連接在執行階段。 這個模式可幫助進行穩固且容易更新的軟體,,因為變更傾向於無法跨元件界限傳播。 建議您之後,即使沒有使用 Stub。 如果您在撰寫新程式碼,遵循 相依性插入 模式非常容易。 如果您為現有的軟體撰寫測試,您可能必須重新建構它。 如果是不實際,您可以考慮使用填充碼。

我們開始與刺激範例,那個以下討論在圖表上。 類別 StockAnalyzer 讀取股票行市並產生一些有趣的結果。 它有一些公用方法,我們要測試。 若要維持簡易性簡單,我們查看這些方法之一,報告特定共用的目前價格非常簡單的一個。 我們要寫入該方法的單元測試。 這個測試的第一份草稿:

        [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

這個測試有問題是立即明顯:共用行市變更,和,以便判斷提示通常會失敗。

另一個問題可能是 StockFeed 元件, StockAnalyzer 使用,仍在開發。 這個方法的程式碼的第一份草稿待測:

        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 類別的工作尚未完成,這個方法可能無法編譯或可能會擲回例外狀況。

介面插入解決這兩個問題。

介面插入套用下列規則:

  • 您的應用程式所有元件程式碼絕對不能明確參考另一個元件的類別,在宣告或 new 陳述式。 相反地,應該宣告變數和參數與介面。 應該由元件的容器只建立元件執行個體。

    由「元件」在這種情況下我們表示類別或您一起開發並更新類別的群組。 通常,元件是在 Visual Studio 專案的程式碼。 因為同時,更新這些分隔類別在元件中較不重要。

    分隔上相對穩定的平台的類別的元件 (例如 System.dll 也是這樣不重要。 這些類別的文字介面將會有自己的程式碼。

使用這樣的介面,因此 StockAnalyzer 程式碼可以分離改善從 StockFeed:

    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 的實作。 在完成應用程式,初始化程式碼會進行連接:

analyzer = new StockAnalyzer(new StockFeed())

會執行這個連接更有彈性的方式。 例如, StockAnalyzer 可以接受無法具現化 IStockFeed 不同的實作不同情況的 Factory 物件。

Hh549174.collapse_all(zh-tw,VS.110).gif產生 Stub

您分隔您要從其他元件測試所使用的類別。 同時讓應用程式更穩固且彈性,並以允許您將元件待測對進行測試的 Stub 介面的實作。

您可以撰寫 Stub 為類別在一般方式。 但是, Microsoft Fakes 讓您以更動態的方式建立每個測試的最適當的 Stub。

若要使用 Stub,您必須首先會從介面定義的 Stub 型別。

將 Fakes 組件。

  1. 在方案總管中,展開您的單元測試專案的 [參考]。

    • 如果您在 Visual Basic 中工作,您必須先在方案總管工具列中的 [顯示所有檔案] ],才能看見參考目錄。
  2. 選取包含介面定義您要建立 Stub 的組件。

  3. 在捷徑功能表上,選擇 [將 Fakes 組件。]。

Hh549174.collapse_all(zh-tw,VS.110).gif撰寫與 Stub 的測試

[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。 對於每個公用型別參考的組件, Microsoft Fakes 機制產生 Stub 類別。 Stub 類別的名稱是衍生自介面的名稱,與「Fakes.Stub」做為前置詞和參數型別的名稱。

Stub 也會產生屬性的 getter 和 setter,為事件和為泛型方法。

Hh549174.collapse_all(zh-tw,VS.110).gif驗證參數值。

您可以驗證該,當元件呼叫另一個元件時,會傳遞正確的值。 您在 Stub 可以將判斷提示,您也可以在測試的主體中儲存值並驗證它。 例如:

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

     // 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

不同類型的 Stub 型別成員

Hh549174.collapse_all(zh-tw,VS.110).gif方法

如本範例所說明,方法可以透過附加 Stub 類別執行個體的委派 Stub。 將 Stub 型別名稱從方法和參數名稱。 例如,下列 IMyInterface 介面和方法 MyMethod:

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

我們將永遠傳回 1 的 Stub 附加在 MyMethod :

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

如果您為函式不提供 Stub, Fakes 將產生會傳回型別的預設值的函式。 如果是數字,則預設值為 0,因此,為類別型別是 null (C#) 或 Nothing (Visual Basic)。

Hh549174.collapse_all(zh-tw,VS.110).gif屬性

屬性 getter 和 setter 會公開為不同的委派,並可以分開被 Stub。 例如,請考慮 IMyInterfaceValue 屬性:

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

我們將委派附加至 Value 的 getter 和 setter 來模擬自動屬性:

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

如果您為 setter 或屬性的 getter 不提供 Stub 方法, Fakes 會儲存值的 Stub,因此, Stub 屬性的運作方式與簡單變數。

Hh549174.collapse_all(zh-tw,VS.110).gif事件

當事件被當委派欄位公開。 因此,所有截短的事件可以藉由觸發事件的支援欄位引發。 請考量下列介面 Stub:

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

若要引發事件, Changed 我們叫用支援委派:

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

Hh549174.collapse_all(zh-tw,VS.110).gif泛型方法

藉由替每個需要的事件提供委派去 Stub 泛型方法。 例如,包含泛型方法的下列介面:

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

您可以撰寫 Stub GetValue<int> 執行個體化的測試:

// 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> , Stub 會呼叫行為。

Hh549174.collapse_all(zh-tw,VS.110).gif虛擬類別 Stub。

在上述範例中, Stub 從介面產生。 您也可以從具有虛擬或抽象成員的類別 Stub。 例如:

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

在這個類別所產生的 Stub,您可以設定 DoAbstract() 和 DoVirtual() 的委派不是方法,不過, DoConcrete()。

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

如果您為虛擬方法不提供委派, Fakes 可以提供預設行為,也可以呼叫基底類別的方法。 若要讓基底方法呼叫,請將 CallBase 屬性:

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

偵錯 Stub

Stub 型別是設計用來提供平滑的偵錯經驗。 根據預設,偵錯工具會在所有產生的程式碼逐步執行指令,因此,應該直接逐步執行至附加至 Stub 的自訂成員實作。

Stub 限制

  1. 使用指標的方法簽章並不支援。

  2. 因為將 Stub 型別依賴虛擬方法分派,所以密封類別或靜態方法不可以是 Stub。 在這種狀況,請使用 使用填充碼將應用程式與其他組件隔離,方便進行單元測試填充碼型別

變更 Stub 預設行為。

每個產生的 Stub 型別中所保留 IStubBehavior 介面的執行個體 IStub.InstanceBehavior (透過屬性)。 無論用戶端呼叫未附加的自訂委派成員,行為都會被呼叫。 如果行為尚未設定,則會使用 StubsBehaviors.Current 屬性所傳回的執行個體。 根據預設,這個屬性會傳回 NotImplementedException 擲回例外狀況的行為。

行為可以設定為任何 Stub 執行個體的屬性 InstanceBehavior 隨時變更。 例如,下列程式碼片段將不會執行任何動作也不會傳回型別的預設值的行為: default(T):

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

行為可能不是透過設定 StubsBehaviors.Current 任何屬性所變更,設定為Stub 全域物件也會變更:

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

外部資源

Hh549174.collapse_all(zh-tw,VS.110).gif指引

測試以搭配使用 Visual Studio 2012RC–第 2 章:單元測試:內部測試

請參閱

概念

使用 Microsoft Fakes 在測試期間隔離程式碼