青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

C++ Programmer's Cookbook

{C++ 基礎} {C++ 高級} {C#界面,C++核心算法} {設計模式} {C#基礎}

使用 Visual Studio Team Test 進行單元測試

演練:使用 Visual Studio Team Test 進行單元測試

發(fā)布日期: 5/24/2005 | 更新日期: 5/24/2005

Mark Michaelis
Itron Corporation

摘要:本演練通過測試驅動開發(fā) (TDD) 和先測試-后編碼 (test-then-code) 的方法學習單元測試。

*
本頁內容
簡介 簡介
開始 開始
創(chuàng)建測試 創(chuàng)建測試
運行測試 運行測試
檢查異常 檢查異常
從數據庫中加載測試數據 從數據庫中加載測試數據
測試視圖 (Test View) 窗口 測試視圖 (Test View) 窗口
增加一個測試數據庫 增加一個測試數據庫
將數據與測試關聯 將數據與測試關聯
實現和重構目標方法 實現和重構目標方法
代碼覆蓋 代碼覆蓋
初始化和清除測試 初始化和清除測試
最佳實踐 最佳實踐
小結 小結

簡介

最新發(fā)布的 Visual Studio Test System (VSTS) 包含了一套用于 Visual Studio Team Test 的完整功能。Team Test 是 Visual Studio 集成的單元測試框架,它支持:

?

測試方法存根 (stub) 的代碼生成。

?

在 IDE 中運行測試。

?

合并從數據庫中加載的測試數據。

?

測試運行完成后,進行代碼覆蓋分析。

另外,Team Test 包含了一套測試功能,可以同時支持開發(fā)人員和測試人員。

在本文中,我們準備演練如何創(chuàng)建Team Test 的單元測試。我們從一個簡單的示例程序集開始,然后在該程序集中生成單元測試方法存根。這樣可以為Team Test 和單元測試的新手讀者提供基本的語法和代碼,同時也很好地介紹了如何快速建立測試項目的結構。然后,我們轉到使用測試驅動開發(fā) (test driven development, TDD) 方法,即在寫產品代碼前先寫單元測試。

Team Test的一個關鍵特點是從數據庫中加載測試數據,然后將其用于測試方法。在演示基本的單元測試后,我們描述如何創(chuàng)建測試數據并集成到測試中。

本文中使用的示例項目包含一個 LongonInfo 類,它封裝了與登錄相關的數據(例如用戶名和密碼)以及一些關于數據的簡單的驗證規(guī)則。最終的類如下圖 1 所示。


1. 最終的 LogonInfo

請注意所有的測試代碼位于一個單獨的項目。這是有道理的,產品代碼應該盡可能少的受測試代碼影響,所以我們不想在產品代碼的程序集中嵌入測試代碼。

開始

首先,我們創(chuàng)建一個名為“VSTSDemo”的類庫項目。默認情況下,為方案創(chuàng)建目錄(Create directory for solution) 復選框被選中。保留此選項可以使我們在 VSTSDemo 項目的同一層目錄創(chuàng)建測試項目。相反,如果不選中此選項,Visual Studio 2005 會將測試項目放在 VSTSDemo 項目的子目錄中。測試項目遵循 Visual Studio 在解決方案文件路徑的子目錄中創(chuàng)建額外項目的規(guī)定。

創(chuàng)建初始的 VSTSDemo 項目后,我們使用 Visual Studio 的解決方案資源管理器將 Class1.cs 文件重命名為 LogonInfo.cs,這樣類名也會被更新為 LogonInfo。然后我們修改構造函數以接受兩個字符串參數:userIdpassword。一旦構造函數的簽名被聲明,我們就可以為構造函數生成測試。


2. LongonInfo 構造函數的上下文菜單的“創(chuàng)建測試 ( Create Tests... ) 菜單項

創(chuàng)建測試

在開始編寫 LogonInfo的任何實現之前,我們遵循 TDD 實踐的規(guī)則,首先編寫測試。TDD 在Team Test 中并不是必需的,但最好在本文的剩余部分遵循 TDD。右鍵單擊 LogonInfo()構造函數,然后選擇“創(chuàng)建測試…”菜單項(如圖 2 所示)。這樣會出現一個對話框,可以在不同的項目中生成單元測試(如圖 3 所示)。默認情況下,項目設置的輸出 (Output) 選項是一個新的 Visual Basic 項目,但是也可以選擇 C# 和 C++ 測試項目。在本文中,我們選擇 Visual C#,然后單擊 OK 按鈕,接著輸入項目名 VSTSDemo.Test。測試項目名稱。


3. 生成單元測試對話框

生成的測試項目包含四個與測試相關的文件。

文件名 目的

AuthoringTest.txt

提供關于創(chuàng)建測試的說明,包括向項目增加其他測試的說明。

LogonInfoTest.cs

包含了用于測試 LogonInfo()的生成測試,以及測試初始化和測試清除的方法。

ManualTest1.mht

提供了一個模板,可以填入手工測試的指令。

UnitTest1.cs

一個空的單元測試類架構,用于放入另外的單元測試。

因為我們不打算對該項目進行手工測試,并且由于已經有了一個單元測試文件,我們將刪除 ManualTest1.mht 和 UnitTest1.cs。

除了一些默認的文件,生成的測試項目還包含了對 Microsoft.VisualStudio.QualityTools.UnitTestFramework和 VSTSDemo 項目的引用。前者是測試引擎運行單元測試需要依賴的測試框架程序集,后者是對我們需要測試的目標程序集的項目引用。

默認情況下,生成的測試方法是包含以下實現的占位符:

清單 1. 生成的測試方法: ConstructorTest(), 位于 VSTSDemo.Test.LogonInfoTest

   
/// <summary>
   ///This is a test class for VSTTDemo.LogonInfo and is intended
   ///to contain all VSTTDemo.LogonInfo Unit Tests
   ///</summary>
   [TestClass()]
   public class LogonInfoTest
   {
      // ...

      /// <summary>
      ///A test case for LogonInfo (string, string)
      ///</summary>
      [TestMethod()]
      public void ConstructorTest()
      {
         string userId = null; // TODO: Initialize to an appropriate value

         string password = null; // TODO: Initialize to an appropriate value

         LogonInfo target = new LogonInfo(userId, password);

         // TODO: Implement code to verify target
         Assert.Inconclusive(
            "TODO: Implement code to verify target");
      }
}

確切的生成代碼會根據測試目標的方法類型和簽名不同而有所不同。例如,向導會為私有成員函數的測試生成反射代碼。在這種特別的情況下,我們需要專門用于公有構造函數測試的代碼。

關于Team Test ,有兩個重要的特性。首先,作為測試的方法由 TestMethodAttribute屬性指定,另外,包含測試方法的類有 TestClassAttribute屬性。這些屬性都可以在 Microsoft.VisualStudio.QualityTools.UnitTesting.Framework 命名空間中找到。Team Test 使用反射機制在測試程序集中搜索所有由 TestClass修飾的類,然后查找由 TestMethodAttribute修飾的方法來決定執(zhí)行的內容。另外一個重要的由執(zhí)行引擎而不是編譯器驗證的標準是,測試方法的簽名必須是無參數的實例方法。因為反射搜索 TestMethodAttribute,所以測試方法可以使用任意的名字。

測試方法 ConstructorTest()首先實例化目標 LongonInfo 類,然后斷言測試是非決定性的(使用Assert.Inconclusive())。當測試運行時,Assert.Inconclusive()說明了它可能缺少正確的實現。在我們的示例中,我們更新 ConstructorTest()方法,讓它檢查用戶名和密碼的初始化,如下所示。

清單 2. 更新的 ConstructorTest() 實現

      
/// <summary>
      ///A test case for LogonInfo (string, string)
      ///</summary>
      [TestMethod()]
      public void ConstructorTest()
      {
         string userId = "IMontoya";

         string password = "P@ssw0rd";

         LogonInfo logonInfo = new LogonInfo(userId, password);

         Assert.AreEqual<string>(userId, logonInfo.UserId,
            "The UserId was not correctly initialized.");
         Assert.AreEqual<string>(password, logonInfo.Password,
            "The Password was not correctly initialized.");
      }

請注意我們的檢查使用 Assert.AreEqual<T>() 方法完成。Assert方法也支持沒有泛型的 AreEqual(),但是泛型版本幾乎總是首選,因為它會在編譯時驗證類型匹配 - 在 CLR 支持泛型前,這種錯誤在單元測試框架中非常普遍。

因為 UserID 和 Password 的實例域還沒有創(chuàng)建,我們需要回頭將其添加到 LogonInfo類中,以便VSTTDemo.Test 項目可以編譯。

即使我們還沒有一個有效的實現,讓我們開始運行測試。如果我們遵循 TDD 方法,我們就應該直到測試證明我們需要這樣的代碼時才去編寫產品代碼。我們僅在建立項目結構時違背此原則,但是一旦項目建立后,就可以容易地始終遵循 TDD 方法。

運行測試

要運行項目中的所有測試,只需要運行測試項目。要實現這一點,我們需要右鍵單擊解決方案資源管理器的VSTSDemo.Test 項目,選擇設置為啟動項目(Set as StartUp Project)。接著,使用菜單項調試->啟動(F5) 或者調試->開始執(zhí)行(不調試)(Ctrl+F5) 開始運行測試。

這時出現測試結果窗口,列出項目中的所有測試。因為我們的項目只包含一個測試,因此只列出了一個測試。開始的時候,測試會處于掛起的狀態(tài),但是一旦測試完成,結果將是我們意料中的失敗(如圖 4 所示)。


4. 執(zhí)行所有測試后的測試結果窗口

圖 4 顯示了測試結果 (Test Results) 窗口。這個特別的屏幕快照除了默認的列外,還顯示了錯誤信息。您可以在列頭上單擊右鍵并選擇菜單項增加/刪除列…以增加或者刪除列。

如果要查看測試的額外細節(jié),我們可以選定測試并雙擊,打開“ConstructorTest[Results]”窗口,如圖 5 所示。


5. 詳細的測試 ConstructorTest [ Results ] 窗口

另外,我們可以右鍵單擊單個測試,然后選擇打開測試(Open Test) 菜單項,進入測試代碼。因為我們已經知道問題在于 LogonInfo 構造函數的實現,我們可以去那里編寫初始化 UserID Password 字段的代碼,使用傳入的參數對它們進行初始化。重新運行測試以驗證測試現在可以通過。

檢查異常

下一步是創(chuàng)建 LongonInfo 類,以提供對 UserID 和 password 的一些驗證。不幸的是,UserIDPassword 字段是公共的,這意味著它們沒有提供任何封裝來確保它們有效。但是在我們將其轉換為屬性并提供驗證前,讓我們編寫一些測試來驗證任何實現的結果都是正確的。

我們首先來編寫一個測試,防止空值 (null) 或空字符串賦值給 UserID。預期結果是,如果空值傳送給構造函數,會引發(fā)一個 ArgumentException異常。測試代碼如清單 3 所示。

清單 3. 使用 ExpectedExceptionAttribute 對異常情況進行測試

      
[TestMethod]
      [ExpectedException(typeof(ArgumentException),
         "A userId of null was inappropriately allowed.")]
      public void NullUserIdInConstructor()
      {
         LogonInfo logonInfo = new LogonInfo(null, "P@ss0word");
      }

      [TestMethod]
      [ExpectedException(typeof(ArgumentException),
         "A empty userId was inappropriately allowed.")]
      public void EmptyUserIdInConstructor()
      {
         LogonInfo logonInfo = new LogonInfo("", "P@ss0word");
      }

請注意對于 ArgumentException沒有 try-catch 代碼塊的顯式測試。不過,兩個測試都包含另外一個屬性 ExpectedException,它接受一個類型參數,以及一個可選的錯誤信息,用于在沒有引發(fā)異常時顯示。當這個單元測試執(zhí)行時,測試框架會顯式地監(jiān)視引發(fā)的 ArgumentException異常,如果方法沒有引發(fā)這個異常,測試將失敗。運行這些測試會證明我們還沒有對 UserID 做任何驗證檢查;因此,測試會失敗,因為沒有引發(fā)預期的異常。

有了失敗的測試,現在可以回到產品代碼進行更新來提供測試需要檢查的功能。在這個例子中,我們將 UserID字段轉換為屬性,并提供驗證檢查(清單 4)。

清單 4. LogonInfo 類中驗證 UserId

   
public class LogonInfo
   {
      public LogonInfo(string userId, string password)
      {
         this.UserId = userId;
         this.Password = password;
      }

      private string _UserId;
      public string UserId
      {
         get { return _UserId; }
         private set
         {
            if (value == null || value.Trim() == string.Empty)
            {
               throw new ArgumentException(
                  "Parameter userId may not be null or blank.");
            }
            _UserId = value;
         }
      }
      
      // ...
}

屬性的實現使用了 C# 2.0 的功能,其中 getter 和 setter 的訪問權限不一致。setter的實現標識為私有,而 getter 實現為公有。這樣 UserID 就不能在 LogonInfo 類外被修改了(除非通過反射機制)。

一旦增加了驗證,我們可以重新運行測試來驗證實現是正確的。我們運行所有的三個測試來驗證 UserID 字段轉換為屬性的重構過程沒有產生任何意外的錯誤。單元測試的真正價值在代碼修改的時候才真正有所體現。一套單元測試可以保證我們在維護和改進代碼的時候沒有破壞代碼。

從數據庫中加載測試數據

對于 LogonInfo 類的下一次修改,我們將提供一個方法來改變密碼。該方法接受舊密碼和新密碼作為參數。另外,我們會驗證密碼符合某種復雜性需求。確切的說,我們將保證密碼符合 Windows Active Directory 的默認需求,即包含以下四種類型字符中的三種:

?

大寫字母

?

小寫字母

?

標點符號

?

數字

另外,我們將檢查密碼最少包含 6 個字符,最多包含 255 個字符。

和之前一樣,我們在編寫實現前先為密碼復雜性需求編寫測試。但是顯然,我們需要提供一個測試值的大集合用于驗證實現。我們不是為每個測試用例創(chuàng)建一個單獨的測試,也不是創(chuàng)建一個循環(huán)來調用一系列的測試用例,我們將創(chuàng)建一個數據驅動測試,它從數據庫中取出所需的數據。

測試視圖 (Test View) 窗口

首先我們定義一個名為 ChangePasswordTest() 的新測試。定義后,從菜單項測試->查看和創(chuàng)建測試(Test->View and Author Tests)為測試方法打開測試視圖窗口,如圖 6 所示:


6. 測試視圖 ( Test view ) 窗口

測試視圖窗口可用來運行指定的測試和瀏覽測試的特定屬性。通過增加額外的列(右鍵單擊列頭并選擇添加/刪除列…),我們可以排序并根據偏好查看測試。有些列來自修飾測試的屬性。例如,添加 OwnerAttribute將在所有者列顯示測試的所有者。其它元數據屬性(如 DescriptionAttribute也可以使用。這些屬性都可以在 Microsoft.VisualStudio.QualityTools.UnitTesting.Framework 命名空間中找到。如果沒有顯式的屬性存在,那么我們可以使用自由形式的 TestPropertyAttribute來為特別的測試方法增加名-值對。

沒有對應列的屬性可以在一個測試的屬性窗口中顯示(選擇一個測試,在右鍵上下文菜單中單擊屬性)。它包含了指定數據連接字符串和用于載入測試數據的表名的屬性。顯然,為了指定有效值,我們需要一個數據庫連接。

增加一個測試數據庫

從服務器資源管理器窗口,我們可以使用創(chuàng)建新的 SQL Server數據庫(Create new SQL Server Database) 菜單項。但是要小心這種方法,如果我們要在其它計算機上執(zhí)行測試的話,我們要保證在一臺服務器上創(chuàng)建數據庫,其它可能執(zhí)行測試的計算機必須能夠訪問該服務器 — 例如一臺用于構建的計算機。

另外一個選擇是僅僅增加一個數據庫文件。使用項目->增加新項 (Project->Add new item...) 允許向項目插入一個 SQL 數據庫文件。這種方法使測試數據和測試項目保持在一起。缺點是如果數據庫變得很大,我們就不想這么做,而寧可提供全局的數據源。

對于本項目中的數據,我們創(chuàng)建一個名為 VSTSDemo.mdf的本地項目數據庫文件。為了向文件加入測試數據,我們使用菜單工具->連接到數據庫 (Tools->Connect to Database),然后指定 VSTSDemo.mdf 文件。然后,從服務器資源管理器窗口我們可以使用設計器加入一個新的表 LongonInfoTest。清單 5 顯示了該表的定義。

清單 5. LogonInfoTestData SQL 腳本

CREATE TABLE dbo.LogonInfoTest
   (
   UserId nchar(256) NOT NULL PRIMARY KEY CLUSTERED,
   Password nvarchar(256) NULL,
   IsValid bit NOT NULL
   )  ON [PRIMARY]
GO

保存表后,我們可以將其打開,然后輸入不同的非法密碼,如下表所示。

UserId Password IsValid

Humperdink

P@w0d

False

IMontoya

p@ssword

False

Inigo.Montoya

P@ssw0rd

False

Wesley

Password

False

將數據與測試關聯

一旦完成表的創(chuàng)建,我們需要將其與測試 InvalidPasswords()聯系起來。從測試 InvalidPasswords的屬性窗口,我們填寫數據連接字符串(Data Connection String) 數據表名 (Data Table Name) 屬性。這樣做將使用附加的屬性 DataSourceAttributeDataTableNameAttribute更新測試。最終的方法 ChangePasswordTest()在清單 6 中顯示。

清單 6. 用于數據驅動測試的測試代碼

      
enum Column
      {
         UserId,
         Password,
         IsValid
      }

      private TestContext testContextInstance;

      /// <summary>
      ///Gets or sets the test context which provides
      ///information about and functionality for the 
      ///current test run.
      ///</summary>
      public TestContext TestContext
      {
         get
         {
            return testContextInstance;
         }
         set
         {
            testContextInstance = value;
         }
      }

      [TestMethod]
      [Owner("Mark Michaelis")]
      [TestProperty("TestCategory", "Developer"), 
      DataSource("System.Data.SqlClient", 
         "Data Source=.\\SQLEXPRESS;AttachDbFilename=\"<Path to the sample .mdf file>";Integrated 
Security=True", "LogonInfoTest", DataAccessMethod.Sequential)] public void ChangePasswordTest() { string userId = (string)TestContext.DataRow[(int)Column.UserId]; string password = (string)TestContext.DataRow[(int)Column.Password]; bool isValid = (bool)TestContext.DataRow[(int)Column.IsValid]; LogonInfo logonInfo = new LogonInfo(userId, "P@ssw0rd"); if (!isValid) { Exception exception = null; try { logonInfo.ChangePassword( "P@ssw0rd", password); } catch (Exception tempException) { exception = tempException; } Assert.IsNotNull(exception, "The expected exception was not thrown."); Assert.AreEqual<Type>( typeof(ArgumentException), exception.GetType(), "The exception type was unexpected."); } else { logonInfo.ChangePassword( "P@ssw0rd", password); Assert.AreEqual<string>(password, logonInfo.Password, "The password was not changed."); } }

清單 6 第一個需要注意的地方是增加了 DataSourceAttribute屬性,它指明了連接字符串、表名和訪問順序。在這個清單中,我們使用數據庫文件名標識數據庫。這樣的優(yōu)點是該文件和測試項目一起遷移,假設它可能會被移動到一個相對的路徑。

第二個注意的地方是 TestContext.DataRow調用。TestContext是在我們運行創(chuàng)建測試向導時由生成器提供的屬性,它在運行時由測試執(zhí)行引擎自動賦值,這樣我們就可以在測試中訪問跟測試環(huán)境關聯的數據。如圖 7 所示。


7. TestContext 關聯

如圖 7 所示,TestContext提供了 TestDirectoryTestName數據,以及 BeginTimer()EndTimer()方法。對 ChangePasswordTest()方法最有意義的是 DataRow屬性。因為 ChangePasswordTest()方法DataSourceAttribute修飾,該屬性指定的表返回每個記錄時,該方法都會被調用一次。這就使測試代碼使用運行中的測試的數據,而且對插入 LongonInfoTest 表的每條記錄重復執(zhí)行測試。如果表包含四條記錄,那么測試將會分別執(zhí)行四次。

使用這樣的數據驅動測試方法,可以很容易的提供額外的測試數據,而不需要編寫任何代碼。一旦需要額外的測試用例,我們需要做的就是向 LongonInfoTest 表增加關聯的數據。盡管我們可以創(chuàng)建兩個獨立的測試來使用單獨的表分別測試有效和無效數據,這個特定的例子合并了這些測試來顯示稍微復雜的數據測試實例。

實現和重構目標方法

現在我們已經有了測試,是時候為測試編寫實現了。使用 C# 重構工具,我們可以右鍵單擊 ChangePassword()方法調用,選擇菜單GenerateMethodStub,然后對于生成的方法提供實現,一旦我們成功地運行了使用所有測試數據的測試,我們也可以開始重構代碼了,LogonInfo 類的最終實現如清單 7 所示。

清單 7. LogonInfo

using System;
using System.Text.RegularExpressions;

namespace VSTTDemo
{
   public class LogonInfo
   {
      public LogonInfo(string userId, string password)
      {
         this.UserId = userId;
         this.Password = password;
      }

      private string _UserId;
      public string UserId
      {
         get { return _UserId; }
         private set
         {
            if (value == null || value.Trim() == string.Empty)
            {
               throw new ArgumentException(
                  "Parameter userId may not be null or blank.");
            }
            _UserId = value;
         }
      }

      private string _Password;
      public string Password
      {
         get { return _Password; }
         private set
         {
            string errorMessage;
            if (!IsValidPassword(value, out errorMessage))
            {
               throw new ArgumentException(
                  errorMessage);
            }
            _Password = value;
         }
      }

      public static bool IsValidPassword(string value, 
         out string errorMessage)
      {
         const string passwordSizeRegex = "(?=^.{6,255}$)";
         const string uppercaseRegex = "(?=.*[A-Z])";
         const string lowercaseRegex = "(?=.*[a-z])";
         const string punctuationRegex = @"(?=.*\d)";
         const string upperlowernumericRegex = "(?=.*[^A-Za-z0-9])";

         bool isValid;
         Regex regex = new Regex(
            passwordSizeRegex +
            "(" + punctuationRegex + uppercaseRegex + lowercaseRegex +
               "|" + punctuationRegex + upperlowernumericRegex + lowercaseRegex +
               "|" + upperlowernumericRegex + uppercaseRegex + lowercaseRegex +
               "|" + punctuationRegex + uppercaseRegex + upperlowernumericRegex +
            ")^.*");

         if (value == null || value.Trim() == string.Empty)
         {
            isValid = false;
            errorMessage = "Password may not be null or blank.";
         }
         else
         {
            if (regex.Match(value).Success)
            {
               isValid = true;
               errorMessage = "";
            }
            else
            {
               isValid = false;
               errorMessage = "Password does not meet the complexity requirements.";
            }
         }
         return isValid;
      }

      public void ChangePassword(
         string oldPassword, string newPassword)
      {
         if (oldPassword == Password)
         {
            Password = newPassword;
         }
         else
         {
            throw new ArgumentException(
               "The old password was not correct.");
         }
      }
   }
}

代碼覆蓋

單元測試的一個關鍵度量是決定在單元測試運行時測試了多少代碼。該度量稱為代碼覆蓋,Team Test 包含了一個代碼覆蓋工具,可以詳細解釋被執(zhí)行代碼的百分率,并突出顯示哪些代碼被執(zhí)行,那些沒有被執(zhí)行。該功能如圖 8 所示。


8. 突出顯示代碼覆蓋

圖 8 顯示了運行所有單元測試后的代碼覆蓋的突出顯示情況。紅色突出顯示說明了我們有產品代碼沒有運行任何單元測試,這說明我們編寫這些代碼時未遵循 TDD 原則,即在編寫實現前先提供測試。

初始化和清除測試

一般來說,測試類不僅包含獨立的測試方法,還包含了不同的對測試進行初始化和清除的方法。實際上,創(chuàng)建測試向導在創(chuàng)建 VSTSDemo.Test 項目時,將一些這樣的方法添加到類 LongonInfoTest 中,見清單 8。

清單 8. 最終的 LogonInfoTest

using VSTTDemo;
using Microsoft.VisualStudio.QualityTools.UnitTesting.Framework;
using System;

namespace VSTSDemo.Test
{
   /// <summary>
   ///This is a test class for VSTTDemo.LogonInfo and is intended
   ///to contain all VSTTDemo.LogonInfo Unit Tests
   ///</summary>
   [TestClass()]
   public class LogonInfoTest
   {

      private TestContext testContextInstance;

      /// <summary>
      ///Gets or sets the test context which provides
      ///information about and functionality for the 
      ///current test run.
      ///</summary>
      public TestContext TestContext
      {
         get
         {
            return testContextInstance;
         }
         set
         {
            testContextInstance = value;
         }
      }

      /// <summary>
      ///Initialize() is called once during test execution before
      ///test methods in this test class are executed.
      ///</summary>
      [TestInitialize()]
      public void Initialize()
      {
         //  TODO: Add test initialization code
      }

      /// <summary>
      ///Cleanup() is called once during test execution after
      ///test methods in this class have executed unless
      ///this test class' Initialize() method throws an exception.
      ///</summary>
      [TestCleanup()]
      public void Cleanup()
      {
         
         //  TODO: Add test cleanup code
      }


      // ...

      [TestMethod]
      // ...
      public void ChangePasswordTest()
      {   
      // ...
      }

   }
}

用于對測試進行設置和清除的方法分別由屬性 TestInitializeAttributeTestCleanupAttribute修飾。在每個這樣的方法中,我們可以加入額外的代碼,它們將會在每個測試前或者測試后運行。這意味著在每次對應于 LongonInfoTest 表的記錄的 ChangePasswordTest()執(zhí)行前,Initialize() Cleanup() 都會被執(zhí)行,每次 NullUserIdInConstructorEmptyUserIdInConstructor執(zhí)行時也會發(fā)生同樣的情況。這樣的方法可以用于向數據庫中插入默認的數據,然后在測試完成時清除插入的數據。例如,我們可以做到在 Initialize()中開始一個事務,然后在清除時回滾同一個事務,這樣一來,如果測試方法使用相同的連接時,數據狀態(tài)會在每次測試執(zhí)行完成時恢復原狀。類似地,測試文件也可以這樣處理。

在調試期間,TestCleanupAttribute修飾的方法可能由于調試器在清除的代碼執(zhí)行前終止運行。由于這個原因,最好在設置測試期間檢查清除情況,并在需要時在設置測試前執(zhí)行清除代碼。關于初始化和清除的其它可用的測試屬性有 AssemblyInitializeAttribute/AssemblyCleanupAttributeClassInitializeAttribute/ClassCleanupAttribute。程序集相關的屬性對整個程序集運行一次,而類相關的屬性對一個特定的測試類的加載運行一次。

最佳實踐

在結束前我們回顧幾種單元測試的最佳實踐。首先,TDD 是非常有價值的實踐。在所有現有的開發(fā)方法中,TDD 可能是多年來根本上改進開發(fā)且投資成本最小的一種。每個 QA 工程師都會告訴您,開發(fā)人員在沒有相應的測試前不會寫出成功的軟件。有了 TDD,實踐是在實現前編寫測試,并且理想情況是,編寫的測試可以成為無需人工參與執(zhí)行的構建腳本的一部分。需要訓練來開始養(yǎng)成習慣,但一旦建立習慣后,不使用 TDD 方法編碼就像開車時不系安全帶一樣。

對于測試本身,有一些額外的原則可以幫助成功進行測試:

?

避免測試產生依賴性,這樣測試需要按照特定的順序執(zhí)行。每個測試都應該是自治的。

?

使用測試初始化代碼驗證測試清除已經成功執(zhí)行,如果沒有則在執(zhí)行測試前重新執(zhí)行清除。

?

在編寫任何產品代碼的實現前編寫測試。

?

對于產品代碼中的每個類創(chuàng)建一個測試類。這樣可以簡化測試的組織,并可以容易地選擇在何處放置每個測試。

?

使用 Visual Studio 生成初始化的測試項目。這樣可以大大減少手工設置測試項目并與產品項目關聯的步驟。

?

避免創(chuàng)建其他依賴計算機的測試,例如依賴特定的目錄路徑的測試。

?

創(chuàng)建模擬對象 (mock object) 來測試接口。模擬對象通常在需要驗證 API 符合所需功能的測試項目中實現。

?

在繼續(xù)創(chuàng)建新的測試前驗證所有測試運行成功。這樣可以保證在破壞代碼后立刻進行修正。

?

可以最大化無需人工參與執(zhí)行的測試代碼。在依賴于手工測試前,必須完全肯定無法采用合理的無需人工參與執(zhí)行的測試方案。

小結

總的來說,VSTS 的單元測試功能本身很好理解。而且盡管本文沒有提到,它還可以通過自定義執(zhí)行引擎進行擴展。此外,它包含了代碼覆蓋分析的功能,這對于評價測試的全面性非常有用。通過使用 VSTS,您可以將測試數目和 bug 數目或編寫的代碼數量進行關聯比較。這為項目的運行狀況提供了很好的指標。

本文介紹了Team Test 產品中的基本單元測試功能,也探討了關于數據驅動測試的一些更加高級的功能。通過開始實踐對代碼進行單元測試,您會為產品的整個生命期建立一套寶貴的測試集。Team Test 通過與 Visual Studio 的強大集成和其它 VSTS 產品線,使這一切變得容易。

Mark Michaelis 在 Itron 公司擔任軟件架構師和講師。他曾經對幾個微軟的產品設計進行檢查,包括 C# 和VSTS。現在他正在撰寫另外一本有關 C# 的書,Essential C# (Addison Wesley)。不使用計算機時,他會陪伴家人,進行戶外運動,或者進行環(huán)球旅行。Mark Michaelis 住在 Spokane, WA。您可以通過 mark@michaelis.net 和他聯系或者訪問他的網絡日志:http://mark.michaelis.net

轉到原英文頁面

翻譯者Luke是微軟公司的軟件工程師,習慣使用C++和C#開發(fā)應用程序。閑暇時間他喜歡音樂,旅游和懷舊游戲,并且愿意幫助MSDN翻譯更多的文章和其他開發(fā)者共享。可以通過ecaijw@msn.com聯系他。

posted on 2006-06-16 11:46 夢在天涯 閱讀(1468) 評論(1)  編輯 收藏 引用 所屬分類: C#/.NETVS2005/2008

評論

# re: 使用 Visual Studio Team Test 進行單元測試 2006-06-16 11:47 夢在天涯

原文:http://www.microsoft.com/china/msdn/library/langtool/vsts/sentvstsover.mspx?mfr=true  回復  更多評論   

公告

EMail:itech001#126.com

導航

統(tǒng)計

  • 隨筆 - 461
  • 文章 - 4
  • 評論 - 746
  • 引用 - 0

常用鏈接

隨筆分類

隨筆檔案

收藏夾

Blogs

c#(csharp)

C++(cpp)

Enlish

Forums(bbs)

My self

Often go

Useful Webs

Xml/Uml/html

搜索

  •  

積分與排名

  • 積分 - 1815684
  • 排名 - 5

最新評論

閱讀排行榜

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
      <noscript id="pjuwb"></noscript>
            <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
              <dd id="pjuwb"></dd>
              <abbr id="pjuwb"></abbr>
              国产亚洲欧美另类中文| 国产精品揄拍500视频| 亚洲激情校园春色| 欧美国产欧美综合 | 亚洲精品乱码久久久久久蜜桃91| 久久久精品tv| 亚洲经典在线看| 最新国产成人在线观看| 欧美日韩一区不卡| 久久精品国产久精国产一老狼| 欧美一区二区在线观看| 亚洲激情影院| 亚洲网站在线观看| 一区二区三区在线观看国产| 欧美激情精品久久久久久变态| 欧美精品在线观看| 久久精品卡一| 欧美精品自拍| 久久久久久国产精品一区| 老司机一区二区| 亚洲男人的天堂在线| 欧美伊人久久久久久午夜久久久久| 激情久久久久久久| 亚洲精品一区在线观看香蕉| 国产嫩草影院久久久久| 亚洲成人在线网| 国产精品视频网| 亚洲高清免费在线| 国产一区二区精品丝袜| 亚洲国产合集| 国产婷婷一区二区| 亚洲精品在线观看视频| 精品88久久久久88久久久| 夜夜嗨av一区二区三区| 在线观看欧美日本| 亚洲一区二区黄色| 亚洲精品一区二区三区在线观看| 午夜精品福利视频| 在线亚洲国产精品网站| 久久久久久精| 欧美一区二区三区免费视| 欧美精品电影| 欧美电影打屁股sp| 海角社区69精品视频| 亚洲午夜精品一区二区| 亚洲免费久久| 免费成人黄色| 久久综合九色综合网站| 国产麻豆精品在线观看| 一区二区免费在线视频| 日韩视频在线播放| 麻豆成人在线观看| 两个人的视频www国产精品| 国产精品裸体一区二区三区| 999亚洲国产精| 亚洲精品午夜精品| 欧美成年人视频网站| 欧美国产第一页| 在线观看一区欧美| 久久久蜜臀国产一区二区| 欧美在线视频在线播放完整版免费观看| 欧美片在线观看| 亚洲美女网站| 夜夜嗨av一区二区三区网页| 欧美激情自拍| 亚洲美女毛片| 亚洲欧美日韩视频一区| 国产精品青草久久| 亚洲影院在线观看| 久久久久久久激情视频| 精品成人国产在线观看男人呻吟| 久久精品国产清高在天天线| 巨乳诱惑日韩免费av| 亚洲国产婷婷香蕉久久久久久99 | 国产精品99久久久久久久vr| 亚洲影院高清在线| 国产精品美女主播在线观看纯欲| 亚洲午夜国产一区99re久久 | 亚洲一区激情| 国产精品视频久久| 欧美亚洲网站| 欧美国产丝袜视频| 国产精品99久久99久久久二8 | 欧美福利视频在线观看| 亚洲人精品午夜| 亚洲欧美日韩一区| 国产一区观看| 欧美福利视频在线观看| 一区二区三欧美| 久久精品一区二区国产| 亚洲国产一区在线观看| 欧美日韩综合视频| 久久久久国产精品一区三寸| 亚洲黄色成人| 午夜一区二区三视频在线观看| 国产亚洲综合精品| 欧美激情精品久久久久| 亚洲一区二区av电影| 久久综合久久综合九色| 99www免费人成精品| 国产欧美精品在线| 欧美激情亚洲另类| 亚洲欧美偷拍卡通变态| 亚洲成人在线视频网站| 亚洲一级免费视频| 伊人夜夜躁av伊人久久| 欧美日韩精品二区第二页| 午夜视频在线观看一区| 亚洲人体大胆视频| 麻豆久久婷婷| 亚洲欧美日韩成人| 亚洲日本成人女熟在线观看| 国产精品视频成人| 欧美精品 日韩| 久久久久久综合网天天| 亚洲免费影院| 99精品国产99久久久久久福利| 久久久噜噜噜久久人人看| 亚洲小视频在线观看| 最新热久久免费视频| 国产一区成人| 国产欧美一区二区精品性| 欧美日韩日日夜夜| 免费成人黄色片| 久久久久久久999| 亚洲欧美乱综合| 在线视频欧美一区| 99热免费精品在线观看| 亚洲国产精品成人综合| 欧美va日韩va| 免费观看在线综合色| 久久av一区二区三区漫画| 亚洲专区欧美专区| 亚洲视频网在线直播| 日韩网站在线| 日韩午夜电影| 9色国产精品| 一卡二卡3卡四卡高清精品视频 | 欧美精品v日韩精品v韩国精品v| 久久精品一区二区三区四区| 亚洲免费在线视频| 亚洲欧美在线观看| 性久久久久久久| 久久国产精品亚洲77777| 午夜综合激情| 久久激情一区| 麻豆精品在线视频| 欧美二区在线| 欧美日产国产成人免费图片| 欧美另类视频| 国产精品久久久久久久9999| 国产精品免费视频xxxx| 国产女主播在线一区二区| 国产视频观看一区| 怡红院av一区二区三区| 亚洲国产精品一区二区三区| 亚洲精品国产精品国自产在线 | 亚洲成色精品| 91久久午夜| 久久久777| 亚洲网友自拍| 亚洲视频综合在线| 日韩视频精品在线| 亚洲免费人成在线视频观看| 久久福利毛片| 欧美大片一区| 国产精品一区二区三区四区| 奶水喷射视频一区| 国产精品手机视频| 亚洲深夜福利| 亚洲欧洲在线播放| 久久人91精品久久久久久不卡| 国产精品一区二区久久久| 在线亚洲一区| 亚洲人成人99网站| 欧美α欧美αv大片| 永久免费精品影视网站| 久久精品一区二区三区不卡牛牛| 正在播放亚洲一区| 欧美日韩中文字幕在线| 一本久久综合亚洲鲁鲁| 91久久一区二区| 欧美国产激情| 日韩视频在线免费| 亚洲精品久久在线| 欧美精品尤物在线| 一本一道久久综合狠狠老精东影业| 欧美国产高清| 久久福利资源站| 亚洲精品在线二区| 久久免费视频这里只有精品| 欧美日韩在线播放一区二区| 1000部国产精品成人观看| 亚洲欧美日韩综合一区| 亚洲国产三级在线| 久久人人爽人人爽| 国产亚洲激情| 午夜影视日本亚洲欧洲精品| 亚洲日本va午夜在线影院| 久久免费视频在线|