分享方式:


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

虛設常式類型是 Microsoft Fakes 架構所提供的重要技術,可讓您輕鬆隔離您所測試的元件與其依賴的其他元件。 虛設常式會作為一小段程式碼,在測試期間取代另一個元件。 使用虛設常式的主要優點是能夠取得一致的結果,讓測試撰寫更為容易。 即使其他元件尚未完全運作,您仍然可以使用虛設常式來執行測試。

若要有效地套用虛設常式,建議以主要相依於介面而非應用程式其他部分中具體類別的方式來設計元件。 此設計方法可促進分離,並降低需要修改另一個部分之某個部分變更的可能性。 在測試方面,此設計模式可替代實際元件的虛設常式實作,從而促進目標元件的有效隔離和精確的測試。

例如,讓我們考慮說明相關元件的圖表:

Diagram of Real and Stub classes of StockAnalyzer.

在此圖表中,受測元件為 StockAnalyzer,通常依賴另一個名為 RealStockFeed 的元件。 不過,由於 RealStockFeed 在每次呼叫其方法時,都會傳回不同的結果,因此會面臨測試的挑戰。 這種變化性使得難以確保 StockAnalyzer 的一致且可靠的測試。

為了在測試期間克服此障礙,我們可以採用相依性插入的做法。 此方法牽涉到撰寫程式碼的方式,使其不會明確提及應用程式另一個元件中的類別。 相反地,您可以定義另一個元件和虛設常式可以針對測試目的實作的介面。

以下是如何在程式碼中使用相依性插入的範例:

public int GetContosoPrice(IStockFeed feed) => feed.GetSharePrice("COOO");

虛設常式限制

檢閱下列虛設常式的限制。

建立虛設常式:逐步指南

讓我們從激勵範例開始練習:上圖中顯示的範例。

建立類別庫

請遵循下列步驟來建立類別庫。

  1. 開啟 Visual Studio 並建立類別庫專案。

    Screenshot of Class Library project in Visual Studio.

  2. 設定專案屬性:

    • 將 [專案名稱] 設定為 StockAnalysis
    • 將 [方案名稱] 設定為 [StubsTutorial]
    • 將專案的目標框架設為 .NET 8.0
  3. 刪除預設檔案 Class1.cs

  4. 新增名為 IStockFeed.cs 的新檔案,並在下列介面定義中複製:

    // IStockFeed.cs
    public interface IStockFeed
    {
        int GetSharePrice(string company);
    }
    
  5. 新增另一個名為 StockAnalyzer.cs 的新檔案,並在下列類別定義中複製:

    // StockAnalyzer.cs
    public class StockAnalyzer
    {
        private IStockFeed stockFeed;
        public StockAnalyzer(IStockFeed feed)
        {
            stockFeed = feed;
        }
        public int GetContosoPrice()
        {
            return stockFeed.GetSharePrice("COOO");
        }
    }
    

建立測試專案

建立練習的測試專案。

  1. 以滑鼠右鍵按一下方案,然後新增名為 MSTest Test Project 的新專案。

  2. 將專案名稱設定為 TestProject

  3. 將專案的目標框架設定為 .NET 8.0

    Screenshot of Test project in Visual Studio.

新增 Fakes 組件

新增專案的 Fakes 組件。

  1. 新增專案參考至 StockAnalyzer

    Screenshot of the command Add Project Reference.

  2. 新增 Fakes 組件。

    1. 在 [方案總管] 中,找出組件參考:

      • 針對舊版 .NET Framework 專案 (非 SDK 樣式),請展開您單元測試專案的參考節點。

      • 針對以 .NET Framework、.NET Core 或 .NET 5.0 或更新版本為目標的 SDK 樣式專案,請展開 [相依性] 節點以在 [組件]、[專案] 或 [套件] 底下尋找要假造的組件。

      • 如果您是使用 Visual Basic,必須選取 [方案總管] 工具列中的 [顯示所有檔案] 才會看見 [參考] 節點。

    2. 選取包含您要用於建立虛設常式之類別定義的組件。

    3. 在捷徑功能表上,選取 [新增 Fakes 組件]

      Screenshot of the command Add Fakes Assembly.

建立單元測試

現在建立單元測試。

  1. 修改預設檔案 UnitTest1.cs 以新增下列 Test Method 定義。

    [TestClass]
    class UnitTest1
    {
        [TestMethod]
        public void TestContosoPrice()
        {
            // Arrange:
            int priceToReturn = 345;
            string companyCodeUsed = "";
            var componentUnderTest = new StockAnalyzer(new StockAnalysis.Fakes.StubIStockFeed()
            {
                GetSharePriceString = (company) =>
                {
                    // Store the parameter value:
                    companyCodeUsed = company;
                    // Return the value prescribed by this test:
                    return priceToReturn;
                }
            });
    
            // 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);
        }
    }
    

    這裡最特別的是 StubIStockFeed 類別。 Microsoft Fakes 機制會針對參考組件中的每一個介面產生虛設常式類別。 該虛設常式類別的名稱衍生自介面的名稱,再加上前置詞「Fakes.Stub」並附加參數類型名稱。

    另外也會為屬性、事件及泛型方法的 getter 及 setter 產生虛設常式。 如需詳細資訊,請參閱使用虛設常式隔離應用程式的各個組件,方便進行單元測試

    Screenshot of Solution Explorer showing all files.

  2. 開啟 [測試總管] 並執行測試。

    Screenshot of Test Explorer.

不同型別成員類型的 Stub

存在不同型別成員類型的虛設常式。

方法

在提供的範例中,在虛設常式類別執行個體附加委派,即可將方法設為虛設常式。 虛設常式類型的名稱衍生自方法名稱及參數。 例如,請考慮下列 IStockFeed 介面及其 方法 GetSharePrice

// IStockFeed.cs
interface IStockFeed
{
    int GetSharePrice(string company);
}

我們會使用 GetSharePriceString 將虛設常式附加至 GetSharePrice

// unit test code
var componentUnderTest = new StockAnalyzer(new StockAnalysis.Fakes.StubIStockFeed()
        {
            GetSharePriceString = (company) =>
            {
                // Store the parameter value:
                companyCodeUsed = company;
                // Return the value prescribed by this test:
                return priceToReturn;
            }
        });

如果您沒有提供方法的虛設常式,Fakes 會產生會傳回傳回型別 default value 的函式。 針對數字,預設值為 0。 針對類別類型,預設值為 null (在 C# 中) 或 Nothing (在 Visual Basic 中)。

屬性

屬性 getter 和 setter 會公開為不同的委派,而且可以個別附加虛設常式。 例如,請考慮 ValueIStockFeedWithProperty 屬性:

interface IStockFeedWithProperty
{
    int Value { get; set; }
}

若要將 Value 的 getter 和 setter 設為虛設常式並模擬 auto-property,您可以使用下列程式碼:

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

如果您未針對屬性的 setter 或 getter 提供虛設常式方法,Fakes 產生的虛設常式會儲存值,讓虛設常式屬性的行為就像簡單變數一樣。

事件

事件會公開為委派欄位,可讓任何設為虛設常式的事件僅透過叫用事件支援欄位來引發。 請考慮下列虛設常式介面:

interface IStockFeedWithEvents
{
    event EventHandler Changed;
}

若要引發 Changed 事件,您可以叫用備份委派:

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

泛型方法

您可以為每個所需的方法具現化提供委派,即可將泛型方法設為虛設常式。 例如,若是下列包含泛型方法的介面:

interface IGenericMethod
{
    T GetValue<T>();
}

您可以將 GetValue<int> 具現化設為虛設常式,如下所示:

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

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

如果程式碼使用任何其他具現化呼叫 GetValue<T>,則虛設常式會執行該行為。

虛擬類別的 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;
    }
}

在這個類別所產生的虛設常式中,您可以設定 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));

變更虛設常式的預設行為

每個產生的虛設常式類別均會透過 IStub.InstanceBehavior 屬性保留一個 IStubBehavior 介面的執行個體。 只要用戶端呼叫成員時沒有附加自訂委派,就會呼叫行為。 如果行為尚未設定,則會使用 StubsBehaviors.Current 屬性所傳回的執行個體。 根據預設,這個屬性傳回的行為會擲回 NotImplementedException 例外狀況。

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

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

對於未使用 StubsBehaviors.Current 屬性設定行為的所有虛設常式物件,也可以全域變更該行為:

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