Share via


單元測試企業應用程式

注意

本電子書於 2017 年春季出版,此後尚未更新。 這本書中有很多仍然有價值的,但一些材料已經過時。

行動裝置應用程式有桌面和 Web 應用程式不需要擔心的獨特問題。 行動使用者會因使用裝置、網路連線、服務可用性和一系列其他因素而有所不同。 因此,行動應用程式應該在真實世界中進行測試,以改善其品質、可靠性和效能。 應用程式上應該執行許多類型的測試,包括單元測試、整合測試和使用者介面測試,而單元測試是最常見的測試形式。

單元測試會採用應用程式的小型單位 (通常是方法),將其與程式碼的其餘部分隔離,並確認其行為符合預期。 其目標是檢查每個功能單位是否如預期般執行,讓錯誤不會在整個應用程式中傳播。 在發生錯誤的當下偵測 Bug,可以更有效率地間接觀察第二次失敗時 Bug 造成的影響。

當單元測試是軟體開發工作流程不可或缺的一部分時,單元測試對程式代碼質量的影響最大。 一旦撰寫方法,應該撰寫單元測試,以驗證方法的行為,以響應標準、界限和不正確的輸入數據案例,並檢查程式代碼所做的任何明確或隱含假設。 或者,使用測試驅動開發,單元測試會在程式碼之前撰寫。 在此案例中,單元測試會同時作為設計檔和功能規格。

注意

單元測試對回歸非常有效,也就是說,用來運作但因錯誤更新而干擾的功能。

單元測試通常會使用 arrange-act-assert 模式:

  • 單元 測試方法的arrange 區段會初始化 物件,並設定傳遞給受測方法的數據值。
  • act段會使用必要的自變數叫用受測方法。
  • 判斷 提示 區段會驗證受測方法的動作是否如預期般運作。

遵循此模式可確保單元測試可讀取且一致。

相依性插入和單元測試

採用鬆散耦合架構的其中一項原因,是其有助於單元測試。 其中一個向 Autofac 註冊的類型是 類別 OrderService 。 下列程式碼範例顯示此類別的大綱:

public class OrderDetailViewModel : ViewModelBase  
{  
    private IOrderService _ordersService;  

    public OrderDetailViewModel(IOrderService ordersService)  
    {  
        _ordersService = ordersService;  
    }  
    ...  
}

類別 OrderDetailViewModelIOrderService 容器具現化物件時 OrderDetailViewModel 所解析的類型具有相依性。 不過,與其建立 OrderService 對象來單元測試 OrderDetailViewModel 類別,而是將 物件取代 OrderService 為模擬,以便進行測試。 圖 10-1 說明此關聯性。

Classes that implement the IOrderService interface

圖 10-1: 實作 IOrderService 介面的類別

這個方法可讓 OrderService 對象在運行時間傳遞至 OrderDetailViewModel 類別,而且為了可測試性,它可讓 OrderMockService 類別在測試時間傳遞至 OrderDetailViewModel 類別。 這種方法的主要優點是,它可讓單元測試執行,而不需要 Web 服務或資料庫等不雜亂無章的資源。

測試MVVM應用程式

您可以從MVVM應用程式測試模型和檢視模型與測試任何其他類別相同,而且可以使用相同的工具和技術,例如單元測試和模擬。 不過,有些模式是典型的模型和檢視模型類別,可受益於特定的單元測試技術。

提示

請使用每個單元測試來測試一個項目。 請勿針對多個層面的單元行為進行單元測試練習。 這樣做會產生難以閱讀和更新的測試。 在解譯失敗時,也可能會導致混淆。

eShopOnContainers 行動裝置應用程式會執行單元測試,其支援兩種不同類型的單元測試:

  • 事實是一律為 true 的測試,其會測試不因條件而異。
  • 理論是僅適用於特定數據集的測試。

eShopOnContainers 行動應用程式隨附的單元測試是事實測試,因此每個單元測試方法都會以 [Fact] 屬性裝飾。

注意

xUnit 測試是由測試執行器執行。 若要執行測試執行器,請針對所需的平臺執行 eShopOnContainers.TestRunner 專案。

測試異步功能

實作 MVVM 模式時,檢視模型通常會以非同步方式叫用服務上的作業。 測試叫用這些作業的程式碼時,通常會使用模擬來取代實際服務。 下列程式碼範例示範如何將模擬服務傳遞至檢視模型來測試非同步功能:

[Fact]  
public async Task OrderPropertyIsNotNullAfterViewModelInitializationTest()  
{  
    var orderService = new OrderMockService();  
    var orderViewModel = new OrderDetailViewModel(orderService);  

    var order = await orderService.GetOrderAsync(1, GlobalSetting.Instance.AuthToken);  
    await orderViewModel.InitializeAsync(order);  

    Assert.NotNull(orderViewModel.Order);  
}

此單元測試會檢查 OrderDetailViewModel 執行個體的 Order 屬性在叫用 InitializeAsync 方法之後是否會有值。 當檢視模型的對應檢視瀏覽至 InitializeAsync 方法時,便會叫用該方法。 如需瀏覽的詳細資訊,請參閱瀏覽

建立 OrderDetailViewModel 執行個體時,它會預期 OrderService 執行個體會指定為引數。 不過,OrderService 會從 Web 服務擷取資料。 因此, OrderMockService 實例是 類別的 OrderService 模擬版本,會指定為建構函式的 OrderDetailViewModel 自變數。 然後,當叫用檢視模型的 InitializeAsync 方法時,會 IOrderService 叫用作業,擷取模擬數據,而不是與Web服務通訊。

測試 INotifyPropertyChanged 實作

實作 INotifyPropertyChanged 介面可讓檢視回應源自檢視模型和模型的變更。 這些變更不限於控件中顯示的數據, 它們也會用來控制檢視,例如導致動畫啟動的檢視模型狀態,或停用控件。

您可以藉由將事件處理常式附加至 PropertyChanged 事件,以及檢查事件是否在設定屬性的新值之後引發事件,以測試單元測試可以直接更新的屬性。 下列程式碼範例會示範這類測試:

[Fact]  
public async Task SettingOrderPropertyShouldRaisePropertyChanged()  
{  
    bool invoked = false;  
    var orderService = new OrderMockService();  
    var orderViewModel = new OrderDetailViewModel(orderService);  

    orderViewModel.PropertyChanged += (sender, e) =>  
    {  
        if (e.PropertyName.Equals("Order"))  
            invoked = true;  
    };  
    var order = await orderService.GetOrderAsync(1, GlobalSetting.Instance.AuthToken);  
    await orderViewModel.InitializeAsync(order);  

    Assert.True(invoked);  
}

此單元測試會叫用 OrderViewModel 類別的 InitializeAsync 方法,這會導致更新其 Order 屬性。 若針對 Order 屬性引發 PropertyChanged 事件,則將會通過單元測試。

測試訊息式通訊

檢視使用 MessagingCenter 類別在鬆散耦合類別之間通訊的模型,可藉由訂閱受測程式碼所傳送的訊息來進行單元測試,如下列程式碼範例所示:

[Fact]  
public void AddCatalogItemCommandSendsAddProductMessageTest()  
{  
    bool messageReceived = false;  
    var catalogService = new CatalogMockService();  
    var catalogViewModel = new CatalogViewModel(catalogService);  

    Xamarin.Forms.MessagingCenter.Subscribe<CatalogViewModel, CatalogItem>(  
        this, MessageKeys.AddProduct, (sender, arg) =>  
    {  
        messageReceived = true;  
    });  
    catalogViewModel.AddCatalogItemCommand.Execute(null);  

    Assert.True(messageReceived);  
}

本單元測試會檢查 CatalogViewModel 是否發佈 AddProduct 訊息,以作為其執行 AddCatalogItemCommand 的回應。 因為 MessagingCenter 類別支援多點傳送訊息訂閱,所以單元測試可以訂閱 AddProduct 訊息,並執行回呼委派作為回應來接收訊息。 這個回呼委派指定為 Lambda 運算式,會設定 boolean 語句用來 Assert 驗證測試行為的欄位。

測試例外狀況處理

您也可以撰寫單元測試來檢查是否會針對不正確動作或輸入擲回特定例外狀況,如下列程式碼範例所示:

[Fact]  
public void InvalidEventNameShouldThrowArgumentExceptionText()  
{  
    var behavior = new MockEventToCommandBehavior  
    {  
        EventName = "OnItemTapped"  
    };  
    var listView = new ListView();  

    Assert.Throws<ArgumentException>(() => listView.Behaviors.Add(behavior));  
}

此單元測試會擲回例外狀況,因為 ListView 控件沒有名為 OnItemTapped的事件。 Assert.Throws<T> 方法是泛型方法,其中 T 是預期例外狀況的類型。 傳遞至 Assert.Throws<T> 方法的引數,是會擲回例外狀況的 Lambda 運算式。 因此,如果 Lambda 運算式擲回 ArgumentException,則將會通過單元測試。

提示

避免撰寫檢查例外狀況訊息字串的單元測試。 例外狀況訊息字串可能會隨著時間而變更,因此依賴其存在的單元測試會被視為不盡完善。

測試驗證

測試驗證實作有兩個層面:測試是否已正確實作任何驗證規則,以及測試 ValidatableObject<T> 類別是否如預期般執行。

因為驗證邏輯通常為輸出相依於輸入的獨立式處理序,所以通常很容易測試。 您應該測試在至少有一個相關聯驗證規則之每個屬性上叫用 Validate 方法的結果,如下列程式碼範例所示:

[Fact]  
public void CheckValidationPassesWhenBothPropertiesHaveDataTest()  
{  
    var mockViewModel = new MockViewModel();  
    mockViewModel.Forename.Value = "John";  
    mockViewModel.Surname.Value = "Smith";  

    bool isValid = mockViewModel.Validate();  

    Assert.True(isValid);  
}

MockViewModel 執行個體中的兩個 ValidatableObject<T> 屬性都有資料時,此單元測試會檢查驗證是否成功。

除了檢查驗證是否成功,驗證單元測試也應該檢查每個 ValidatableObject<T> 執行個體其 ValueIsValidErrors 屬性的值,以確認類別是否正常執行。 下列程式碼範例示範執行這項作業的單元測試:

[Fact]  
public void CheckValidationFailsWhenOnlyForenameHasDataTest()  
{  
    var mockViewModel = new MockViewModel();  
    mockViewModel.Forename.Value = "John";  

    bool isValid = mockViewModel.Validate();  

    Assert.False(isValid);  
    Assert.NotNull(mockViewModel.Forename.Value);  
    Assert.Null(mockViewModel.Surname.Value);  
    Assert.True(mockViewModel.Forename.IsValid);  
    Assert.False(mockViewModel.Surname.IsValid);  
    Assert.Empty(mockViewModel.Forename.Errors);  
    Assert.NotEmpty(mockViewModel.Surname.Errors);  
}

此單元測試會檢查當 MockViewModelSurname 屬性沒有任何資料,且每個 ValidatableObject<T> 執行個體的 ValueIsValidErrors 屬性都已正確設定時,驗證會失敗。

摘要

單元測試會採用應用程式的小型單位 (通常是方法),將其與程式碼的其餘部分隔離,並確認其行為符合預期。 其目標是檢查每個功能單位是否如預期般執行,讓錯誤不會在整個應用程式中傳播。

測試中物件的行為可以隔離,方法是將相依物件取代為模擬相依物件行為的模擬物件。 這可讓單元測試執行,而不需要未經處理的資源,例如 Web 服務或資料庫。

從 MVVM 應用程式測試模型和檢視模型,與測試任何其他類別的方式相同,且可以使用相同的工具和技術。