要生成高質量的軟件,需要在測試階段進行大量的工作,這可能是軟件開發過程中成本最高、工作量最大的部分。 從最簡單的功能黑盒測試到重量級的方法,包括定理證明程序以及形式化需求說明,有很多方法可以提高測試可靠性和效率。 但是,測試并不總是能達到必要的細致程度,經常缺乏規范和方法體系。
十多年來,Microsoft 在其內部開發流程中成功應用了基于模型的測試 (MBT)。 事實證明,對于各種內部和外部軟件產品而言,MBT 是非常成功的方法。 這些年來,這種方法采用得越來越多。 相對來說,它在測試界已廣為接受(尤其是與測試方法中的其他“形式化”方法相比時)。
Spec Explorer 是 Microsoft 的 MBT 工具,它擴展了 Visual Studio,提供高度集成的開發環境,可以創建行為模型,它也是圖形分析工具,可用于檢查這些模型的有效性以及基于這些模型生成測試用例。 我們認為,這個工具是一個分界點,它促進了 MBT 這項高效方法在 IT 行業中的應用,緩和自然學習曲線,提供最先進的開發環境。
本文概要介紹 MBT 和 Spec Explorer 背后的主要概念,通過一個案例研究說明 Spec Explorer 的主要功能。 我們也希望本文提供的實際經驗規則,可以幫助您了解,何時應考慮使用 MBT 這種質量保證方法體系來解決特定測試問題。 您不應在一切測試方案中盲目使用 MBT。 很多時候,其他方法(如傳統測試)可能是更好的選擇。
盡管不同的 MBT 工具提供不同的功能,有時在概念上稍有差異,但對于“執行 MBT”的含義,這些工具是一致的?;谀P偷臏y試自動基于模型生成測試過程。
模型通常是手動創建的,包括系統需求和預期行為。 具體到 Spec Explorer,是基于面向狀態的模型自動生成測試用例。 測試用例包括測試序列和測試預期。 測試序列是從模型推斷的,負責推動待測系統 (SUT) 達到不同狀態。 測試預期跟蹤 SUT 的演變過程,確定它是否符合模型指定的行為,并且作出判定。
模型是 Spec Explorer 項目中的主要部分之一。 它在稱為模型程序的構造中指定。 您可以采用任何 .NET 語言(如 C#)來編寫模型程序。 這種程序由一組與已定義狀態交互的規則構成。 模型程序與稱為 Cord 的腳本編寫語言組合,Cord 是 Spec Explorer 項目中第二重要的部分。 這樣,可以指定行為描述來配置瀏覽和測試模型的方式。 模型程序與 Cord 腳本結合后,可針對 SUT 創建可測試的模型。
當然,Spec Explorer 項目中第三重要的部分是 SUT。 不是一定要向 Spec Explorer 提供 SUT 才能生成測試代碼(這是 Spec Explorer 的默認模式),因為生成的代碼是直接從可測試模型推斷的,并不與 SUT 進行任何交互。 您可以獨立于模型評估和測試用例生成階段“離線”執行測試用例。 但是,如果提供 SUT,則 Spec Explorer 可以驗證是否已明確定義從模型到實現的綁定。
我們來看一個示例,它介紹如何在 Spec Explorer 中構建可測試的模型。 此示例中的 SUT 是一個簡單的聊天系統,只有一個聊天室,用戶可以登錄和注銷。 用戶登錄后,可以請求已登錄用戶的列表,可以向所有用戶發送廣播消息。 聊天服務器總是確認這些請求。 請求和響應是異步的,這意味著它們可以混在一起。 就像一般的聊天系統,一位用戶發送的多條消息會按順序接收。
使用 MBT 的優點之一是,由于必須形式化行為模型,您可以獲得大量反饋來了解需求。 在早期階段,就能發現歧義、矛盾和缺少上下文的情況。 因此,系統需求務必是準確而形式化的,如:
R1. Users must receive a response for a logon request. R2. Users must receive a response for a logoff request. R3. Users must receive a response for a list request. R4. List response must contain the list of logged-on users. R5. All logged-on users must receive a broadcast message. R6. Messages from one sender must be received in order.
Spec Explorer 項目從測試系統的角度使用操作來描述與 SUT 的交互。 這些操作可以是表示從測試系統到 SUT 的觸發動作的調用操作,可以是捕獲來自 SUT 的響應(如果有)的返回操作,還可以是表示從 SUT 發送的自治消息的事件操作。 調用/返回操作是阻塞操作,因此它們在 SUT 中由單個方法來表示。 這些都是默認操作聲明,而“event”關鍵字用于聲明事件操作。 圖 1 是聊天系統中的操作聲明。
圖 1 操作聲明
// Cord code config ChatConfig { action void LogonRequest(int user); action event void LogonResponse(int user); action void LogoffRequest(int user); action event void LogoffResponse(int user); action void ListRequest(int user); action event void ListResponse(int user, Set<int> userList); action void BroadcastRequest( int senderUser, string message); action void BroadcastAck(int receiverUser, int senderUser, string message); // ... }
聲明操作之后,下一步是定義系統行為。 在此示例中,用 C# 描述模型。 用類字段建模系統狀態,用規則方法建模狀態轉換。 規則方法確定在模型程序中可基于當前狀態采取的步驟,以及執行每一步后狀態如何更新。
因為此聊天系統實際上是由用戶與系統之間的交互組成,模型的狀態即是用戶及其狀態的集合(請參閱圖 2)。
圖 2 模型的狀態
/// <summary> /// A model of the MS-CHAT sample. /// </summary> public static class Model { /// <summary> /// State of the user. /// </summary> enum UserState { WaitingForLogon, LoggedOn, WaitingForList, WatingForLogoff, } /// <summary> /// A class representing a user /// </summary> partial class User { /// <summary> /// The state in which the user currently is. /// </summary> internal UserState state; /// <summary> /// The broadcast messages that are waiting for delivery to this user. /// This is a map indexed by the user who broadcasted the message, /// mapping into a sequence of broadcast messages from this same user. /// </summary> internal MapContainer<int, Sequence<string>> waitingForDelivery = new MapContainer<int,Sequence<string>>(); } /// <summary> /// A mapping from logged-on users to their associated data. /// </summary> static MapContainer<int, User> users = new MapContainer<int,User>(); // ... }
可以看到,定義模型的狀態與定義一般的 C# 類沒有太大區別。 規則方法是用于描述在何種狀態下可以激活操作的 C# 方法。 規則方法還描述在操作激活時,對模型的狀態應用何種更新。 下面的“LogonRequest”示例說明如何編寫規則方法:
[Rule] static void LogonRequest(int userId) { Condition.IsTrue(!users.ContainsKey(userId)); User user = new User(); user.state = UserState.WaitingForLogon; user.waitingForDelivery = new MapContainer<int, Sequence<string>>(); users[userId] = user; }
此方法描述操作“LogonRequest”的激活條件和更新規則,該操作先前已在 Cord 代碼中聲明。 此規則主要說明:
此時,大部分建模工作已完成。 現在,我們定義一些“機器”,以便瀏覽系統的行為和獲得一些相關信息。 在 Spec Explorer 中,機器指瀏覽單元。 機器有一個名稱以及一個用 Cord 語言定義的關聯行為。 您也可以將一個機器與其他機器組合以形成更復雜的行為。 我們看看聊天模型的幾個示例機器:
machine ModelProgram() : Actions { construct model program from Actions where scope = "Chat.Model" }
我們定義的第一個機器是所謂的“模型程序”機器。 它使用“construct model program”指令告訴 Spec Explorer 基于 Chat.Model 命名空間中的規則方法瀏覽模型的全部行為。
machine BroadcastOrderedScenario() : Actions { (LogonRequest({1..2}); LogonResponse){2}; BroadcastRequest(1, "1a"); BroadcastRequest(1, "1b"); (BroadcastAck)* }
第二個機器是一個“方案”,是用類似正則表達式的方式來定義的操作模式。 方案通常由“模型程序”機器組成,用以分割完整行為,如下所示:
machine BroadcastOrderedSlice() : Actions { BroadcastOrderedScenario || ModelProgram }
“||”操作符用于在兩個參與的機器之間創建“同步并行組合”。 結果行為將僅包含可在兩個機器上同步的步驟(我們說的“同步”,是指使用相同的參數列表執行相同的操作)。 在圖 3 所示的圖中瀏覽此機器結果。
在圖 3 中的圖中可以看到,組合的行為與方案機器和模型程序機器一致。 這是非常有效的方法,可以獲取復雜行為的較簡單子集。 另外,當系統具有無限狀態空間(如聊天系統中那樣)時,分割完整行為可以生成有限的子集,更適合進行測試。
我們來分析此圖中的不同實體。 圓形狀態是可控制的狀態。 它們是為 SUT 提供觸發操作的狀態。 棱形狀態是可觀察的狀態。 這些狀態預期 SUT 會發生一個或多個事件。 測試預期(預期的測試結果)已經在圖中編碼,包括事件步驟及其參數。 具有多個傳出事件步驟的狀態稱為非確定性狀態,因為 SUT 在執行時提供的事件不是在建模時確定的。 觀察圖 3 中的瀏覽圖包含幾個非確定性狀態:S19、S20、S22 等。
瀏覽圖對于理解系統很有用,但它仍然不適合用于測試,因為它不是“測試正常”形式。 如果一個行為不包含任何具有多個傳出調用-返回步驟的狀態,我們就說該行為是測試正常形式。 在圖 3 中的圖中,可以看到 S0 明顯不符合此規則。 要將此類行為轉換為測試正常形式,可以使用測試用例構造簡單創建新機器。
machine TestSuite() : Actions where TestEnabled = true { construct test cases where AllowUndeterminedCoverage = true for BroadcastOrderedSlice }
此構造生成新行為,方法是遍歷初始行為并以測試正常形式生成跟蹤。 遍歷標準是覆蓋到邊緣。 初始行為中的每個步驟都會遍歷至少一次。 圖 4 中的圖形顯示了這樣遍歷后的行為。
要實現測試正常形式,具有多個調用-返回步驟的狀態將按每個步驟進行分割。 不會分割事件步驟,這些步驟始終完全保留,因為事件是 SUT 在運行時可以進行的選擇。 必須準備好測試用例才能處理任何可能的選擇。
Spec Explorer 可以基于測試正常形式行為生成測試套件代碼。 所生成的測試代碼的默認形式是 Visual Studio 單元測試。 您可以使用 Visual Studio 測試工具或 mstest.exe 命令行工具直接執行此類測試套件。 生成的測試代碼是用戶可讀的,可以很方便地調試:
#region Test Starting in S0 [Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute()] public void TestSuiteS0() { this.Manager.BeginTest("TestSuiteS0"); this.Manager.Comment("reaching state \'S0\'"); this.Manager.Comment("executing step \'call LogonRequest(2)\'"); Chat.Adapter.ChatAdapter.LogonRequest(2); this.Manager.Comment("reaching state \'S1\'"); this.Manager.Comment("checking step \'return LogonRequest\'"); this.Manager.Comment("reaching state \'S4\'"); // ... }
測試代碼生成器是高度可自定義的,可配置為生成面向不同測試框架(如 NUnit)的測試用例。
Spec Explorer 安裝程序包括這個完整的聊天模型。
使用基于模型的測試有利有弊。 最明顯的好處是,在完成可測試的模型后,按一下按鈕就能生成測試用例。 此外,模型必須預先形式化,這樣才能實現對需求不一致的早期檢測,幫助團隊在預期行為方面保持正確。 請注意,編寫手動測試用例時,已經有“模型”,但它沒有形式化,只是存在于測試者的腦海中。 MBT 迫使測試團隊清晰地傳達出其有關系統行為的預期,并使用清楚的結構將這些預期編寫出來。
另一個明顯的優點是項目維護成本較低。 系統行為的更改或新增功能可通過更新模型反映出來,這通常比逐個更改手動測試用例簡單得多。 有時,僅僅確定需要更改的測試用例就是一項非常耗時的任務。 請注意,模型編寫也是獨立于實現或實際測試的工作。 這就是說,團隊中的不同成員可以同時進行不同的任務。
缺點是經常需要進行思維調整。 這可能是這個方法的重大挑戰之一。 大家都知道的一個最重要的問題是,IT 從業者沒有時間嘗試新工具,使用這個方法的學習曲線不容忽視。 應用 MBT 可能還需要進行一些流程更改,這也可能造成一些阻礙,具體取決于團隊。
另一個不利之處是,與手動編寫的傳統測試用例相比,必須提前進行更多工作,因此需要花更多時間才能生成第一個測試用例。 另外,測試項目需要有足夠的復雜度,才值得進行投資。
幸運的是,我們認為有幾條經驗規則可幫助確定何時適合使用 MBT。 第一個特征是,系統狀態集無限,可以用不同的方式滿足需求。 系統是反應式或分布式,或具有異步或非確定性交互的系統,這是另一個特征。 另外,如果方法有很多復雜參數,也說明適合用 MBT。
如果符合這些條件,MBT 都有重大意義,可以節省大量測試工作。 Microsoft Blueline 是這方面的示例,在這個項目中,數百個協議驗證為 Windows 協議遵從性計劃的一部分。 在這個項目中,我們使用 Spec Explorer 來驗證實際協議行為的協議文檔的技術準確性。 這是繁重的工作,Microsoft 花費了 250 個人年進行測試。 Microsoft Research 驗證了一項統計信息研究,該項研究表明,使用 MBT 為 Microsoft 節省了 50 個人年的測試工作,換句話說,與傳統測試方法相比,省去了大約 40% 的工作。
基于模型的測試是非常強大的方法,在傳統測試方法的基礎上增加了一種系統的方法。 Spec Explorer 是成熟的工具,它在高度集成、最先進的開發環境中使用 MBT 概念,是免費的 Visual Studio Power Tool。
原文轉自:https://msdn.microsoft.com/zh-cn/magazine/dn532205