在.NET開發中的單元測試工具之(1)——NUnit

 NUnit介紹

  NUnit是一個專門針對於.NET來寫的單元測試框架,它是xUnit體系中的一員,在xUnit體系中還有針對Java的JUnit和針對C++的CPPUnit,在開始的時候NUnit和xUnit體系中的大多數的作法同樣,僅僅是將Smalltalk或者Java版本轉換而來,可是在.NET2.0以後它加入了一些特有的作法。NUnit的官方網站是:http://www.nunit.org/,目前的最新版本是:2.6.2。

NUnit下載與安裝

  NUnit的每一個版本都提供了兩種形式的下載:安裝文件和免安裝方式,分別是*.msi格式和*.zip格式。前者須要安裝才能使用,而且會在安裝過程當中建立一些快捷方式和註冊NUnit的dll到GAC,這樣之後編寫NUnit測試類的時候添加NUnit的dll就像添加.Net Framework的dll同樣。若是是下載的zip格式的文件,則不會建立快捷方式和註冊dll,在編寫單元測試類時須要手動指定NUnit的dll的路徑。
  NUnit的運行有三種方式:命令行和圖形用戶界面。以周公當前電腦上安裝的NUnit2.5.10爲例,安裝路徑爲:C:\Program Files (x86)\NUnit 2.5.10,其下有三個目錄:bin、doc和samples。在doc目錄下是軟件的文檔(英文),在samples目錄下則是一些樣例代碼。若是是採用免安裝模式的話,運行NUnit就須要運行bin目錄下的文件,在bin目錄下有net-1.1和net-2.0兩個文件夾,分別對應.net的不一樣版本。
  下面介紹如何以不一樣的方式啓動NUnit:
  命令行模式:運行nunit-console.exe。
  圖形用戶界面模式:運行nunit.exe。
  並行(parallel)模式:運行pnunit-launcher.exe。
  注意:.Net2.0版本的NUnit是使用/platform:anycpu參數來編譯的,咱們知道這樣的結果是運行在x86的系統上會被JIT編譯成32位的程序,而在x64的系統上會被JIT編譯成64位的程序。若是使用NUnit在x64系統上測試32位的程序就會帶來問題。爲了不這個問題,可使用nunit-agent-x86.exe/nunit-x86.exe來測試,由於在編譯的時候使用了/platform:x86做爲編譯參數。
  下圖是運行NUnit的GUI界面:

NUnit的經常使用Attribute標記

  這些都是能夠用來做爲類或者方法的屬性,它們都是System.Attribute類的直接或間接子類,有以下:
  Category:用來將測試分類。這個在主界面能夠看到Tests/Categories兩個選項卡,若是給方法標記了Category屬性就會在Categories選項卡中看獲得。
  Combinatorial:用來未來測試時須要測試各類可能的組合,好比以下代碼:
  [Test, Combinatorial]
  public void MyTest(
  [Values(1, 2, 3)] int x,
  [Values("A", "B")] string s)
  {
      string value = x + s;
      Assert.Greater(2, value.Length);
  }
  測試時實際會測試6種狀況:MyTest(1, "A")/MyTest(1, "B")/MyTest(2, "A")/MyTest(2, "B")/MyTest(3, "A")/MyTest(3, "B")。
  Culture:設置測試時的語言環境,這對咱們測試一些語言敏感的場景下有用,好比DateTime.ToString()在不一樣語言環境下獲得的字符串並不相同。
  Description:用於指定測試對應的描述,若是選擇將測試結果生成XML文件,那麼就會在XML文件中看到這些描述。
  ExpectedException:指出執行測試時將會拋出Exception。
  Explicit:若是測試的類或者方法使用此Attribute,那麼在使用帶GUI的NUnit測試時這個類或者方法必須在界面上選定纔會被執行。
  Explicit:忽略某個測試類或者方法。
  Maxtime:測試方法最大執行的毫秒數,若是程序的執行時間超過指定數值,那麼就會被認爲測試失敗。
  Random:用於指定如何隨機生成參數來測試方法。以下面的代碼:
  [Test]
  public void TestDemo1(
  [Values(1, 2, 3)] int x,
  [Random(-10,10,2)] int y)
  {
      Assert.Greater(x + y, 0);
  }
  表示方法TestDemo1會生成6個測試,1,2,3分別做爲參數x的值與兩次從-10到10之間的隨機數y組成6次測試。
  Range:指定參數的方法,以下面的方法:
  [Test]
  public void TestDemo2(
  [Range(0, 11, 4)] int x)
  {
      Assert.AreEqual(x%3,0);
  }
  表示從0開始遞增,步長爲4,且不大於11。
  Repeat:將重複測試的次數。
  RequiresMTA:表示測試時須要多線程單元(multi-threaded apartment)。
  RequiresSTA:表示測試時須要單線程單元(single-threaded apartment)。
  SetUp:在每一個測試方法開始以前執行的初始化操做。在NUnit 2.5以前要求每一個類只能有一個帶SetUp屬性的實例方法,但在NUnit 2.5以後則沒有次數和必須是實例方法的限制。
  TearDown:與SetUp的做用相反,是在每一個測試方法執行結束以後執行的方法。在NUnit 2.5以前要求每一個類只能有一個帶SetUp屬性的實例方法,但在NUnit 2.5以後則沒有次數和必須是實例方法的限制。
  Test:用來標記須要測試的方法,在NUnit 2.5以前只能用於標記實例方法,在NUnit 2.5以後則能夠用於標記靜態方法。
  TestCase:標記方法具備參數而且提供了在測試時須要的參數。以下面的代碼:
  [TestCase(12, 3, 4)]
  [TestCase(12, 2, 6)]
  [TestCase(12, 4, 3)]
  public void DivideTest(int n, int d, int q)
  {
      Assert.AreEqual(q, n / d);
  }
  將會執行三次測試,至關於:
  [Test]
  public void DivideTest()
  {
      Assert.AreEqual(4,12/3);
  }
  [Test]
  public void DivideTest()
  {
      Assert.AreEqual(6,12/2);
  }
  [Test]
  public void DivideTest()
  {
      Assert.AreEqual(3,12/4);
  }
  TestFixture:標記一個類可能具備[Test]/[SetUp]/[TearDown]方法,但這個類不能是抽象類。
  TestFixtureSetUp:標記在類中全部測試方法執行以前執行的方法。在NUnit 2.5以前只能在類中將此標記最多使用於一個實例方法,在NUnit 2.5以後則能夠標記多個方法,並且不限於實例方法還能夠用於靜態方法。
  TestFixtureTearDown:標記在類中全部測試方法執行以後再執行的方法。在NUnit 2.5以前只能在類中將此標記最多使用於一個實例方法,在NUnit 2.5以後則能夠標記多個方法,並且不限於實例方法還能夠用於靜態方法。
  Timeout:標記被測試的方法最大的執行時間,若是超出標記的時間,則會被取消執行而且被標記爲測試失敗。
  Values:標記做爲測試方法的一系列的參數。前面的代碼實例中就有用法實例。

NUnit的斷言(Assertions)

  斷言是全部基於xUnit單元測試系列的核心,NUnit經過NUnit.Framework.Assert類提供了豐富的斷言。具體說來,NUnit總共提供了11個類別的斷言,它們是:
  Equality Asserts:用於斷言對象是否相等方面的斷言,主要表現爲兩個方法的重載:Assert.AreEqual()和Assert.AreNotEqual()兩種形式的重載,重載參數包括了常見的基本數值類型(int/float/double等)和引用類型(表現爲使用object做爲參數).
  Identity Asserts:用於判斷引用類型的對象是不是同一個引用的斷言及斷言對象是否存在於某個集合中,如Assert.AreSame、Assert.AreNotSame及Assert.Contains。
  Condition Asserts:用於某些條件的斷言,如:Assert.IsTrue、Assert.True、Assert.IsFalse、Assert.False、Assert.IsNull、Assert.Null、Assert.IsNotNull、Assert.NotNull、Assert.IsNaN、Assert.IsEmpty及Assert.IsNotEmpty。
  Comparisons Asserts:用於數值及實現了IComparable接口的類型之間的斷言,如Assert.Greater(大於)、Assert.GreaterOrEqual(大於或等於)、Assert.Less(小於)、Assert.LessOrEqual(小於或等於)。
  Type Asserts:用於類型之間的判斷,好比判斷某個實例是不是某一類型或者是從某個類型繼承,如:Assert.IsInstanceOfType、Assert.IsNotInstanceOfType、Assert.IsAssignableFrom、Assert.IsNotAssignableFrom。在NUnit 2.5以後就增長了泛型方法,如Assert.IsInstanceOf<T>、Assert.IsNotInstanceOf<T>、Assert.IsAssignableFrom<T>、Assert.IsNotAssignableFrom<T>。。
  Exception Asserts:有關異常方面的斷言,如Assert.Throws/Assert.Throws<T>、Assert.DoesNotThrow、Assert.Catch/Assert.Catch<T>。
  Utility Methods:用於精確控制測試過程,總共有四個方法,分別是:Assert.Pass、Assert.Fail、Assert.Ignore、Assert.Inconclusive。Assert.Pass和Assert.Fail是相反的,前者是表示將當即終止測試並將測試結果標識爲成功經過測試,後者是當即終止測試並將測試結果標識爲測試失敗。Assert.Ignore表示忽略測試,這個標記能夠用於標識測試方法或者測試的類。
  StringAssert:用於字符串方面的斷言,提供的方法有StringAssert.Contains、StringAssert.StartsWith、StringAssert.EndsWith、StringAssert.AreEqualIgnoringCase及StringAssert.IsMatch。
  CollectionAssert:關於集合方面的斷言,提供的方法有CollectionAssert.AllItemsAreInstancesOfType、CollectionAssert.AllItemsAreNotNull、CollectionAssert.AllItemsAreUnique、CollectionAssert.AreEqual、CollectionAssert.AreEquivalent、CollectionAssert.AreNotEqual、CollectionAssert.AreNotEquivalent、CollectionAssert.Contains、CollectionAssert.DoesNotContain、CollectionAssert.IsSubsetOf、CollectionAssert.IsNotSubsetOf、CollectionAssert.IsEmpty、CollectionAssert.IsNotEmpty和CollectionAssert.IsOrdered。
  FileAssert:用於文件相關的斷言,主要提供兩個方法:FileAssert.AreEqual和FileAssert.AreNotEqual。
  DirectoryAssert:用於文件夾的斷言,提供的方法有:DirectoryAssert.AreEqual、DirectoryAssert.AreNotEqual、DirectoryAssert.IsEmpty、DirectoryAssert.IsNotEmpty、DirectoryAssert.IsWithin和DirectoryAssert.IsNotWithin。

NUnit的使用

  第一次打開NUnit時會是一個空白界面,以下圖所示:
  首先咱們須要建立一個NUnit項目,點擊[File]->[New Project]會彈出一個保存NUnit項目的對話框,選擇合適的路徑並輸入合適的名稱(注意文件後綴名爲.nunit),而後點擊保存按鈕,這樣就建立了一個NUnit測試項目。之後咱們就能夠再次打開這個項目了。
  此時這個NUnit項目中還不包含任何單元測試用例,咱們須要建立包含測試用例的項目。打開Visual Studio建立一個類庫項目(在真實項目中一般作法是向當前解決方案中添加類庫項目,這樣便於解決dll引用問題),接着咱們須要添加NUnit的引用,這取決於咱們是採用安裝方式仍是免安裝方式,一般狀況下咱們只須要添加對nunit.framework(對應的dll是unit.framework.dll)的引用就夠了。
  這裏周公採用的示例代碼以下:
 
  
  
  
  
  1. using System; 
  2. using System.Collections.Generic; 
  3. using System.Linq; 
  4. using System.Text; 
  5. using NUnit.Framework; 
  6.  
  7. namespace UnitTestDemo 
  8.     [TestFixture] 
  9.     public class NUnitTestDemo 
  10.     { 
  11.         private IList<int> intList = new List<int>(); 
  12.  
  13.         [SetUp] 
  14.         [Category("NA")] 
  15.         public void BeforeTest() 
  16.         { Console.WriteLine("BeforeTest"); } 
  17.  
  18.         [TestFixtureSetUp] 
  19.         [Category("NA")] 
  20.         public void BeforeAllTests() 
  21.         { Console.WriteLine("BeforeAllTests"); } 
  22.  
  23.         [TearDown] 
  24.         [Category("NA")] 
  25.         public void AfterTest() 
  26.         { Console.WriteLine("AfterTest"); } 
  27.  
  28.         [TestFixtureTearDown] 
  29.         [Category("NA")] 
  30.         public void AfterAllTests() 
  31.         { Console.WriteLine("AfterAllTests"); } 
  32.  
  33.         [Test] 
  34.         [Category("NA")] 
  35.         public void Test1() 
  36.         { Console.WriteLine("Test1"); } 
  37.  
  38.         [Test] 
  39.         [Category("NA")] 
  40.         public void Test2() 
  41.         { Console.WriteLine("Test2"); } 
  42.  
  43.         [Test] 
  44.         public void TestFloat() 
  45.         { 
  46.             float value = 0.9999999999999999999999999999f; 
  47.             //value = 0.9999999999999999999999999999; 
  48.             Console.WriteLine("float value:" + value); 
  49.             Assert.AreEqual(value, 1f); 
  50.             Console.WriteLine("TestFloat");  
  51.         } 
  52.  
  53.         [Test] 
  54.         public void TestDouble() 
  55.         { 
  56.             double value = 0.9999999999999999999999999999d; 
  57.             Console.WriteLine("double value:" + value);  
  58.             Assert.AreEqual(value, 1d); 
  59.             Console.WriteLine("Test2");  
  60.         } 
  61.  
  62.         [Test] 
  63.         public void TestDecimal() 
  64.         { 
  65.             decimal value = 0.9999999999999999999999999999M; 
  66.             Console.WriteLine("decimal value:" + value);  
  67.             Assert.AreEqual(value, 1M); 
  68.             Console.WriteLine("Test2");  
  69.         } 
  70.  
  71.         [Test,Repeat(3)] 
  72.         public void TestIntList2() 
  73.         { 
  74.             Assert.AreEqual(0, intList.Count); 
  75.         } 
  76.  
  77.         [Test] 
  78.         public void TestIntList1() 
  79.         { 
  80.             intList.Add(1); 
  81.             Assert.AreEqual(1, intList.Count); 
  82.         } 
  83.  
  84.         [TestCase(12, 3, 4)] 
  85.         [TestCase(12, 2, 6)] 
  86.         [TestCase(12, 4, 3)] 
  87.         public void DivideTest(int n, int d, int q) 
  88.         { 
  89.             Assert.AreEqual(q, n / d); 
  90.         } 
  91.  
  92.         [Test, Combinatorial,Description("This is used for show Combinatorial")] 
  93.         public void MyTest( 
  94.         [Values(1, 2, 3)] int x, 
  95.         [Values("A""B")] string s) 
  96.         { 
  97.             string value = x + s; 
  98.             Assert.Greater(2, value.Length); 
  99.         } 
  100.  
  101.         [Test] 
  102.         public void TestDemo1( 
  103.         [Values(1, 2, 3)] int x, 
  104.         [Random(-10,10,2)] int y) 
  105.         { 
  106.             Assert.Greater(x + y, 0); 
  107.         } 
  108.  
  109.         [Test] 
  110.         public void TestDemo2( 
  111.         [Range(0, 11, 4)] int x) 
  112.         { 
  113.             Assert.AreEqual(x%3,0); 
  114.         } 
  115.     } 
  編譯項目生成dll。咱們就能夠在NUnit主界面上點擊[Project]->[Add Assembly...]來添加剛纔編譯生成的dll,加載成功後界面以下所示:
  點擊界面上的[Run]按鈕就能夠開始測試了。注意這種方式下是測試全部的測試方法,若是咱們只想測試某幾個方法,能夠勾選方面前面的複選框(默認狀況下複選框不出現,須要按照點擊[Tools]->[Setting]打開設置界面,而後點擊在[GUI]下面找到[Tree Display],勾選上「Show CheckBoxes」便可)。
  若是咱們只是想單獨測試某個方法,那就更簡單了——直接雙擊那個測試方法便可。
  有時候咱們進行測試時還會用到一些config文件裏面的配置信息,如在app.config/web.config中保存數據庫鏈接字符串信息及其餘的配置信息,爲了能讓NUnit測試時能讀取app.config/web.config中保存的配置信息,咱們須要對NUnit進行配置。
  爲了演示,咱們制定如下信息:
  項目名稱:UnitTestDemo
  項目位置:D:\BlogCode\UnitTestDemo\
  項目編譯模式(Debug/Release):Debug
  爲了演示剛纔的如何對config文件中保存的數據進行測試,咱們在剛纔的代碼基礎上編寫了三個測試用例,代碼以下:
  
  
  
  
  1. [Test] 
  2. public void Test0_51CTOBlog() 
  3.     StringAssert.AreEqualIgnoringCase(ConfigurationManager.AppSettings["51ctoBlog"], "http://zhoufoxcn.blog.51cto.com"); 
  4.  
  5. [Test] 
  6. public void Test0_CSDNBlog() 
  7.     StringAssert.AreEqualIgnoringCase(ConfigurationManager.AppSettings["CSDNBlog"], "http://blog.csdn.net/zhoufoxcn"); 
  8.  
  9. [Test] 
  10. public void Test0_SinaWeiBo() 
  11.     StringAssert.AreEqualIgnoringCase(ConfigurationManager.AppSettings["SinaWeiBo"], "http://weibo.com/zhoufoxcn"); 
  同時在app.config文件的appSettings節點增長如下數據:
  
  
  
  
  1. <appSettings> 
  2.     <add key="51ctoBlog" value="http://zhoufoxcn.blog.51cto.com"/> 
  3.     <add key="CSDNBlog" value="http://blog.csdn.net/zhoufoxcn"/> 
  4.     <add key="SinaWeiBo" value="http://weibo.com/zhoufoxcn"/> 
  5. </appSettings> 
  若是不在NUnit上作任何設置,咱們會獲得錯誤的結果,以下圖所示:

  這時,咱們能夠按照以下步驟配置,點擊[Project]-[Edit...]打開以下界面:

  在上圖的界面中設置ApplicationBase爲當前要測試的dll所在的路徑,本例中爲:D:\BlogCode\UnitTestDemo\bin\Debug(注意若是複製全路徑到文本框中NUnit會自動更改成相對路徑),由於當前項目是名爲UnitTestDemo的類庫項目,因此對應config文件名稱爲UnitTestDemo.dll.config,將其填入Configuration File Name後面的文本框中,而後咱們再次點擊[Run]按鈕就會看到測試經過。

總結

  做爲xUnit體系中的一員,NUnit確實給.Net開發人員進行單元測試帶來了很多方便,在早期咱們一直都是使用NUnit進行單元測試的。可是也存在着一些不足之處,好比:1.在xUnit體系中的JUnit是在測試每一個方法時都是新生成一個實例,而在NUnit中確實一個TestFixture只會生成一個實例,這樣一來若是對要包含單元測試類中的實例數據進行更改會可能會影響到其它的測試方法(像JUnit那樣每次都生成一個實例則不會產生這種狀況)。2.早期大多數人覺得像JUnit中同樣,[SetUp]、[TearDown]只會在全部測試前、後分別執行一次,實際狀況是在每一個測試前、後都會執行一次,爲了達到JUnit中[SetUp]、[TearDown]這樣的效果,只能新增TestFixtureSetUp、TestFixtureTearDown屬性。除此以外,還存在一些缺點和不足。
  因此本篇只是簡單介紹了NUnit的一些用法,雖然NUnit提供了至關多的斷言及Attribute,但實際用到的並很少,在這裏介紹它是爲介紹另外一個.NET單元測試工具做鋪墊。
  周金橋
  2013-01-03
相關文章
相關標籤/搜索