天天看點

使用 TestApi 進行錯誤注入測試 (msdn)

錯誤注入測試是指有意向待測試的應用程式中注入錯誤,然後運作該應用程式以檢驗其錯誤處理情況的過程。 錯誤注入測試可采取多種不同的形式。 在本月的專欄中,我将介紹如何使用 TestApi 庫的元件,在運作時向 .NET 應用程式中引入錯誤。

要想了解我在本專欄中所講述的内容,最好是看一下圖 1 所示的螢幕快照。 該螢幕快照顯示我正在一個名為 TwoCardPokerGame.exe 的虛拟 .NET WinForm 應用程式上進行錯誤注入測試。 一個名為 FaultHarness.exe 的 C# 程式正在指令 shell 中運作。 它改變了待測試應用程式的正常行為,是以當使用者第三次單擊标記為 Evaluate 的按鈕時,應用程式将引發異常。 在這種情況下,Two Card Poker 應用程式不能妥善地處理應用程式異常,進而導緻系統生成的消息框。

使用 TestApi 進行錯誤注入測試 (msdn)

圖 1 運作中的錯誤注入測試

讓我們進一步看看此方案,考慮一些相關細節。 從指令 shell 啟動 FaultHarness.exe 時,工具會在背景準備分析代碼,該代碼截取 TwoCard­PokerGame.exe 的正常代碼執行。 這一過程稱為錯誤注入會話。

錯誤注入會話使用 DLL 來啟動對調用應用程式 button2_Click 方法的監視,該方法是标記為 Evaluate 的按鈕的事件處理程式。 錯誤注入會話已經過配置,這樣,當使用者前兩次單擊 Evaluate 按鈕時,應用程式按代碼編寫的方式運作,但第三次單擊時,錯誤會話會導緻應用程式引發 System.ApplicationException 類型的異常。

錯誤會話記錄會話活動并對一組檔案進行日志記錄,以測試主機。 請注意,在圖 1 中,前兩次單擊應用程式 Deal-Evaluate 可工作正常,但第三次單擊生成異常。

接下來,我将簡要介紹待測試的虛拟 Two Card Poker Game 應用程式,提供并詳細說明圖 1 所示的 FaultHarness.exe 程式代碼,并就何時适合使用錯誤注入測試以及何時更适合使用其他技術提供一些提示。 雖然 FaultHarness.exe 程式本身十分簡單,大多數複雜工作由 TestApi DLL 在背景執行,但了解和修改我在此處提供的代碼來滿足您自己的測試方案需求要求您充分了解 .NET 程式設計環境。 也就是說,即使您是 .NET 初學者,您也應當能夠輕松了解我介紹的内容。 我相信,您将發現探讨錯誤注入的趣味性,這對于您的工具集來說可能也是有益的補充。

待測試的應用程式

我使用的待測試虛拟應用程式是一個簡單而卻具有代表性的 C# WinForm 應用程式,它模拟一種稱為 Two Card Poker 的假想紙牌遊戲。 該應用程式由兩個主要元件組成:TwoCardPokerGame.exe 提供 UI,TwoCardPokerLib.dll 提供基礎功能。

為了建立遊戲 DLL,我啟動了 Visual Studio 2008,然後從“檔案”|“建立項目”對話框中選擇 C# 類庫模闆。 我将該庫命名為 TwoCardPokerLib。 圖 2 提供了該庫的整體結構。 TwoCardPokerLib 的代碼太長,無法在本文中完整地提供。 本文随附的代碼下載下傳中提供了 TwoCardPokerLib 庫的完整源代碼以及 FaultHarness 錯誤注入工具。

圖 2 TwoCardPokerLib 庫

using System;
namespace TwoCardPokerLib {
  // -------------------------------------------------
  public class Card {
    private string rank;
    private string suit;
    public Card() {
      this.rank = "A"; // A, 2, 3, . . ,9, T, J, Q, K
      this.suit = "c"; // c, d, h, s
    }
    public Card(string c) { . . . }
    public Card(int c) { . . . }
    public override string ToString(){ . . . }
    public string Rank { . . . }
    public string Suit { . . . }
    public static bool Beats(Card c1, Card c2) { . . . }
    public static bool Ties(Card c1, Card c2) { . . . }
  } // class Card

  // -------------------------------------------------
  public class Deck {
    private Card[] cards;
    private int top;
    private Random random = null;

    public Deck() {
      this.cards = new Card[52];
      for (int i = 0; i < 52; ++i)
        this.cards[i] = new Card(i);
      this.top = 0;
      random = new Random(0);
    }

    public void Shuffle(){ . . . }
    public int Count(){ . . . } 
    public override string ToString(){ . . . }
    public Card[] Deal(int n) { . . . }
    
  } // Deck

  // -------------------------------------------------
  public class Hand {
    private Card card1; // high card
    private Card card2; // low card
    public Hand(){ . . . }
    public Hand(Card c1, Card c2) { . . . }
    public Hand(string s1, string s2) { . . . }
    public override string ToString(){ . . . }
    private bool IsPair() { . . . }
    private bool IsFlush() { . . . }
    private bool IsStraight() { . . . }
    private bool IsStraightFlush(){ . . . }
    private bool Beats(Hand h) { . . . }
    private bool Ties(Hand h) { . . . }
    public int Compare(Hand h) { . . . }
    public enum HandType { . . . }
    
 } // class Hand

} // ns TwoCardPokerLib      

應用程式 UI 代碼

完成基礎 TwoCardPokerLib 庫代碼後,我建立了一個虛拟 UI 元件。 我使用 C# WinForm 應用程式模闆在 Visual Studio 2008 中建立了一個新項目,并将我的應用程式命名為 TwoCardPokerGame。

使 用 Visual Studio 設計器,我将一個 Label 控件從工具箱集合中拖到應用程式設計圖面上,并将該控件的 Text 屬性由“textBox1”修改為“Two Card Poker”。然後,我另外添加了兩個 Label 控件(“Your Hand”和“Computer’s Hand”)、兩個 TextBox 控件、兩個 Button 控件(“Deal”和“Evaluate”)和一個 ListBox 控件。 我沒有改變八個控件中任何一個控件的預設控件名稱,如 textBox1、textBox2 和 button1 等。

準備好設計後,輕按兩下 button1 控件使 Visual Studio 為該按鈕生成一個事件處理程式架構,并在代碼編輯器中加載檔案 Form1.cs。 此時,我在解決方案資料總管視窗中右鍵單擊 TwoCardPokerGame 項目,然後從上下文菜單中選擇“添加引用”選項,并指向檔案 TwoCardPokerLib.dll。 在 Form1.cs 中,我添加了一個 using 語句,以便不必完全限定庫中的類名稱。

接下來,我向應用程式添加了四個類作用域靜态對象:

namespace TwoCardPokerGame {
  public partial class Form1 : Form {
    static Deck deck;
    static Hand h1;
    static Hand h2;
    static int dealNumber; 
...      

對象 h1 是針對使用者的 Hand,h2 是針對計算機的 Hand。 然後,我向 Form 構造函數添加了一些初始化代碼:

public Form1() {
  InitializeComponent();
  deck = new Deck();
  deck.Shuffle();
  dealNumber = 0;
}      

Deck 構造函數建立一副撲克牌,按照從梅花 A 到黑桃 K 的順序共計 52 張,Shuffle 方法

随機排列這副撲克牌中紙牌的順序。

接下來,我向 button1_Click 方法添加代碼邏輯,如圖 3 所示。 對于兩手牌中的每一手,我調用 Deck.Deal 方法從 deck 對象中删除兩張牌。 然後,我将這兩張牌傳遞給 Hand 建構函數,并在 TextBox 控件中顯示這手牌的分值。 請注意,button1_Click 方法通過在 ListBox 控件中顯示消息來處理任何異常。

圖 3 處理紙牌

private void button1_Click(
  object sender, EventArgs e) { 

  try  {
    ++dealNumber;
    listBox1.Items.Add("Deal # " + dealNumber);
    Card[] firstPairOfCards = deck.Deal(2);
    h1 = new Hand(firstPairOfCards[0], firstPairOfCards[1]);
    textBox1.Text = h1.ToString();

    Card[] secondPairOfCards = deck.Deal(2);
    h2 = new Hand(secondPairOfCards[0], secondPairOfCards[1]);
    textBox2.Text = h2.ToString();
    listBox1.Items.Add(textBox1.Text + " : " + textBox2.Text);
  }
  catch (Exception ex) {
    listBox1.Items.Add(ex.Message);
  }
}      

接下來,在 Visual Studio 設計器視窗中輕按兩下 button2 控件,以自動生成該控件的事件處理程式

架構。 我添加了一些簡單代碼來比較兩個 Hand 對象,并在 ListBox 控件中顯示一條消息。 請注意 button2_Click 方法并不直接處理任何異常:

private void button2_Click(
  object sender, EventArgs e) {
  int compResult = h1.Compare(h2);
  if (compResult == -1)
    listBox1.Items.Add(" You lose");
  else if (compResult == +1)
    listBox1.Items.Add(" You win");
  else if (compResult == 0)
    listBox1.Items.Add(" You tie");

  listBox1.Items.Add("-------------------------");
}      

錯誤注入工具

在建立如圖 1 所示的錯誤注入工具前,我将關鍵 DLL 下載下傳到測試主機上。 這些 DLL 是名為 TestApi 的 .NET 庫集合的一部分,可在 testapi.codeplex.com 中找到。

TestApi 庫是與軟體測試相關的實用工具的集合。 TestApi 庫包含一組托管代碼錯誤注入 API。 (有關這些 API 的更多資訊,請通路 blogs.msdn.com/b/ivo_manolov/archive/2009/11/25/9928447.aspx 。)我下載下傳最新的錯誤注入 API 版本,在本例中為 0.4 版,然後解壓縮下載下傳内容。 我将簡要地介紹下載下傳内容和放置錯誤注入庫的位置。

0.4 版支援對使用 .NET Framework 3.5 建立的應用程式進行錯誤注入測試。 TestApi 庫正處于開發過程中,是以應檢視 CodePlex 站點來了解我在本文中提供的技術是否有更新。 此外,您需要在 Bill Liu 的部落格中檢視是否有更新和提示,Bill Liu 是 TestApi 錯誤注入庫的主要開發人員,網址是 blogs.msdn.com/b/billliu/ 。

為了建立錯誤注入工具,我在 Visual Studio 2008 中建立了一個新項目,然後選擇 C# 控制台應用程式模闆。 我将該應用程式命名為 FaultHarness,并向該程式模闆添加了一些最簡短的代碼(參見圖 4 )。

圖 4 FaultHarness

using System;
namespace FaultHarness {
  class Program {
    static void Main(string[] args) {
      try {
        Console.WriteLine("/nBegin TestApi Fault Injection environmnent session/n");

        // create fault session, launch application

        Console.WriteLine("/nEnd TestApi Fault Injection environment session");
      }
      catch (Exception ex) {
        Console.WriteLine("Fatal: " + ex.Message);
      }
    }
  } // class Program
} // ns      

我按 <F5> 鍵建構并運作工具架構,該操作在 FaultHarness 根檔案夾中建立了一個 /bin/Debug 檔案夾。

TestApi 下載下傳包含兩個關鍵元件。 第一個是 TestApiCore.dll,位于解壓縮下載下傳的 Binaries 檔案夾中。 我将該 DLL 複制到 FaultHarness 應用程式的根目錄中。 然後在解決方案資料總管視窗中右鍵單擊 FaultHarness 項目,選擇“添加引用”,并指向 TestApiCore.dll。 接着,我将一個 Microsoft.Test.FaultInjection 的 using 語句添加到我的錯誤工具代碼頂部,以便該工具代碼能直接通路 TestApiCore.dll 中的功能。 此外,我添加了一個 System.Diagnostics 的 using 語句,因為我希望從該命名空間通路 Process 和 ProcessStartInfo 類,我将在稍後為您介紹這一點。

錯誤注入下載下傳中的第二個關鍵元件是名為 FaultInjectionEngine 的檔案夾。 該檔案夾包含 32 位和 64 位版本的 FaultInjectionEngine.dll。 我将整個 Fault­InjectionEngine 檔案夾複制到包含 FaultHarness 可執行檔案的檔案夾中,在本例中為 C:/FaultInjection/FaultHarness/bin/Debug/。 我使用的 0.4 版錯誤注入系統要求 FaultInjectionEngine 檔案夾的位置與可執行工具的位置相同。 此 外,系統要求待測試的二進制應用程式位于與可執行工具相同的檔案夾中,是以我将 TwoCardPokerGame.exe 和 TwoCard­PokerLib.dll 檔案複制到 C:/FaultInjection/FaultHarness/bin/Debug/ 中。

總 而言之,使用 TestApi 錯誤注入系統時,最好生成工具架構并加以運作,進而建立 /bin/Debug 工具目錄,然後将 TestApiCore.dll 檔案放入工具根目錄,将 FaultInjectionEngine 檔案夾放入 /bin/Debug,同樣将待測試的二進制應用程式(.exe 和 .dll)放入 /bin/Debug。

使用 TestApi 錯誤注入系統要求您指定待測試的應用程式、待測試應用程式中将觸發錯誤的方法、觸發錯誤的條件以及将觸發的錯誤類型:

string appUnderTest = "TwoCardPokerGame.exe";
string method = 
  "TwoCardPokerGame.Form1.button2_Click(object, System.EventArgs)";
ICondition condition =
  BuiltInConditions.TriggerEveryOnNthCall(3);
IFault fault =
  BuiltInFaults.ThrowExceptionFault(
    new ApplicationException(
    "Application exception thrown by Fault Harness!"));
FaultRule rule = new FaultRule(method, condition, fault);      

請注意,因為系統要求待測試應用程式位于與可執行工具相同的檔案夾中,是以待測試的可執行應用程式的名稱不需要指向其位置的路徑。

指定将觸發注入錯誤的方法名稱是 TestApi 錯誤注入初學者常見的問題根源。 該方法名稱必須完全符合 Name­space.Class.Method(args) 格式。 我的首選方法是利用 ildasm.exe 工具檢查待測試的應用程式,幫助自己确定觸發方法的簽名。 從特定 Visual Studio 工具指令 shell 中啟動 ildasm.exe,指向待測試的應用程式,然後輕按兩下目标方法。 圖 5 顯示了一個利用 ildasm.exe 檢查 button2_Click 方法簽名的示例。

使用 TestApi 進行錯誤注入測試 (msdn)

圖 5 利用 ILDASM 檢查方法簽名

指定觸發方法簽名時,不要使用方法傳回類型,不要使用參數名稱。 得到正确的方法簽名有時需要反複嘗試。 例如,第一次嘗試确定目标 button2_Click 時,我使用:

TwoCardPokerGame.Form1.button2_Click(object,EventArgs)      

我必須将其更正為:

TwoCardPokerGame.Form1.button2_Click(object,System.EventArgs)      

TestApi 下載下傳包括一個 Documentation 檔案夾,該檔案夾包含提供正确指導的概念文檔,指導使用者如何正确構造不同類型的方法簽名,包括構造函數、泛型方法、屬性和重載運算符。 在這裡,我确定的目标是位于待測試應用程式中的方法,但原本也可以确定基礎 Two­CardPokerLib.dll 中的方法為目标,例如:

string method = "TwoCardPokerLib.Deck.Deal(int)"      

指定觸發方法後,下一步是指定将錯誤注入待測試應用程式的條件。 在本例中,我使用的是 TriggerEveryOnNthCall(3),正如您所看到的,每當第三次調用觸發方法時它便注入一個錯誤。 TestApi 錯誤注入系統有一組簡潔的觸發條件,包括 TriggerIfCalledBy(method) 和 TriggerOnEveryCall 等。

指定觸發條件後,下一步是指定将注入待測試系統的錯誤的類型。 我使用的是 BuiltInFaults.ThrowExceptionFault。 除了異常錯誤外,TestApi 錯誤注入系統具有内置的傳回類型錯誤,允許在運作時将錯誤傳回值注入待測試的應用程式。 例如,這将導緻觸發方法傳回值 -1(可能不正确):

IFault f = BuiltInFaults.ReturnValueFault(-1)      

在指定錯誤觸發方法、條件和錯誤類型後,下一步是建立一個新 FaultRule,并将該規則傳遞給新 FaultSession:

FaultRule rule = new FaultRule(method, condition, fault);
Console.WriteLine(
  "Application under test = " + appUnderTest);
Console.WriteLine(
  "Method to trigger injected runtime fault = " + method);
Console.WriteLine(
  "Condition which will trigger fault = On 3rd call");
Console.WriteLine(
  "Fault which will be triggered = ApplicationException");
FaultSession session = new FaultSession(rule);      

所有的預備工作就緒後,編寫錯誤工具代碼的最後一部分是以程式設計方式在錯誤會話環境中啟動待測試的應用程式:

ProcessStartInfo psi = 
  session.GetProcessStartInfo(appUnderTest);
Console.WriteLine(
  "/nProgrammatically launching application under test");
Process p = Process.Start(psi);
p.WaitForExit();
p.Close();      

當您執行錯誤工具時,該工具會在錯誤會話中啟動待測試的應用程式,同時 FaultInjection­Engine.dll 會監視當觸發條件為真時觸發方法的調用情況。 在這裡,測試是手動執行的,但也可以在錯誤會話中運作測試自動化。

當錯誤會話運作時,有關該會話的資訊将記錄到目前目錄中,該目錄是包含可執行錯誤工具和可執行的待測試應用程式的目錄。 您可以檢查這些日志檔案,幫助解決在開發錯誤注入工具時可能遇到的任何問題。

讨論

我在此處提供的示例和說明應當可以帶您入門,幫助您為自己的待測試應用程式建立錯誤注入工具。 與作為軟體開發過程一部分的任何活動一樣,您具有有限資源,是以,您應分析執行錯誤注入測試的得與失。 對某些應用程式而言,建立錯誤注入測試所需的工作可能并不值得,但也存在許多錯誤注入測試至關重要的測試方案。 設想一下控制醫療裝置或飛行系統的軟體。 在此類情況下,應用程式必須絕對可靠,并且能正确處理各種異常錯誤。

錯誤注入測試無疑具有諷刺意味。 也就是說,如果您能預料可能發生異常的情況,則常常可以在理論上以程式設計方式防範異常,并進行測試來獲得該防範行為的正确行為。 不過,即使在這類情況下,錯誤注入測試對阻止異常的發生也是非常有用的。 另外,可以注入難以預測的錯誤,例如 System.OutOfMemoryException。

錯誤注入測試與變化測試相關,有時二者會發生混淆。 在變化測試中,有意将錯誤注入待測試的系統中,但随後針對錯誤系統執行現有測試套件,以檢查測試套件是否捕獲了新産生的錯誤。 變化測試是一種評估測試套件有效性的方法,并最終擴大了測試案例範圍。 正如您在本文中看到的一樣,錯誤注入測試的主要目的是确定待測試系統是否能正确地處理錯誤。

繼續閱讀