NUnit是一個單元測試架構,專門針對于.NET來寫的,它是是xUnit的一員。NUnit完全由C#語言來編寫,并且編寫時充分利用了許多.NET的特性,比如反射,客戶屬性等等.
最重要的一點是它适合于所有.NET語言.
單元測試:
作為程式員在開發過程中,不可避免地要對軟體的類及其方法進行測試。
在Web頁面的測試過程中,好多朋友喜歡把測試結果使用Response.Write()顯示在頁面上。而在類庫元件的開發時,由于沒有可視化界面,有時不得不為該類庫添模一個測試的項目(Console,WinForm,ASP.NET等),在該項目中調用類庫元件的功能,以檢視測試結果。
上面這種測試不是我們推薦的方法,因為我們在測試的時候應遵循以下原則:
盡量不要破壞原有的代碼結構,即不要在原代碼中添加一些備援的測試代碼。
測試子產品應盡可能完整地展現測試結果。
測試子產品不應用完即扔掉,要儲存以備後續的維護測試。
一、NUnit運作界面
《圖1》
在右邊有一個進度條,如果所有測試案例運作成功,就為綠色,反之如果有一個不成功,則為紅色,但也有黃色的.
綠色 描述目前所執行的測試都通過
黃色 意味某些測試忽略,但是這裡沒有失敗
紅色 表示有失敗
左邊的樹狀目錄是我們們編寫的每一個測試單元。
底部的狀态條
狀态:當所有測試完成時,狀态變為Completed.運作測試中,狀态是Running: <test-name>
Test Cases:說明加載的程式集中測試案例的總個數。這也是測試樹裡葉子節點的個數。
Tests Run: 已經完成的測試個數。
Failures: 到目前為止,所有測試中失敗的個數.
Time: 顯示運作測試時間(以秒計)
二、在VS2008中配置NUnit進行測試
1.建立一個類庫項目。
2.在解決方案中的項目圖示上右擊,選擇“屬性”。
3.點選左邊的“調試”标簽
4.在“啟動操作”中選擇“啟動外部程式”,并指明NUnit程式的運作路徑。
5.在啟動選項中的“工作目錄”下,指明目前類庫項目的DLL檔案的所在路徑。
《圖2》
6.運作目前類庫項目,我們會發現NUnit被啟動起來了。
三、安裝Unit與使用
請到http://www.nunit.org/download.html下載下傳NUnit,然後輕按兩下安裝即可。
1.我們要使用NUnit對類庫元件進行測試時,一般我們會建立一個測試類檔案或建立一個測試項目。
2.為測試項目添加引用“nunit.framework”
3.在測試類檔案中添加using NUnit.Framework;語句
4.在該類檔案或測試項目中編寫測試代碼,然後使用NUnit運作我們的測試代碼以觀察測試結果。
四、NUnit中的常用Attribute
所有NUnit屬性都包含在Nunit.Framework命名空間裡。測試項目必須引用架構的程式集,即nunit.framework.dll.
1.TestFixtureAttribute
用來修飾測試類。這個屬性标記一個類包含了測試方法。
被TestFixtureAttribute修飾的類需滿足以下限制
a.必須是一個public類型,否則NUnit不會識别它。
b.它必須有一個預設的構造子,否則Nunit不能建構他。
c.構造子不應該有任何方面的負面影響,因為在一個對話的過程中,NUnit可能構造類多次。
如:
using System;
using NUnit.Framework;
[TestFixture]
public class SuccessTests
{
// ...
}
2.TestAttribute
用來修飾測試方法。Test屬性标記某個類的某個方法為一個測試方法,而且此類必需已經标記為一個TestFixture。
一個測試方法的簽名定義如下:
public void MethodName()
注意:測試方法必須沒有參數。如果程式員将測試方法标記為不正确的簽名,它不會運作。
如:
using System;
using NUnit.Framework;
[TestFixture]
public class SuccessTests
{
[Test]
public void Add()
{
//...
}
}
3.SetUpAttribute
用來修飾方法。所屬的類必需已經标記為一個TestFixture。一個TestFixture可以僅有一個SetUp方法。如果有多個定義,TestFixture也會編譯成功,但是測試不會運作。SetUpAttribute标記的方法是在每個測試方法被調用之前來完成的。
如:
using System;
using NUnit.Framework;
[TestFixture]
public class SuccessTests
{
[SetUp]
public void Init()
{ }
[TearDown]
public void Dispose()
{ }
[Test]
public void Add()
{ }
}
SetUp屬性是從任何基類繼承而來。是以,如果基類已經定義了一個SetUp方法,那麼這個方法會在每個派生的類的測試方法之前調用。如果你打算在派生類裡加入更多的SetUp功能,你需要将方法标記為适當的屬性,然後調用基類方法。
SetUpAttribute使用案例:
如:我們以測試加減法為例
using System;
using NUnit.Framework;
[TestFixture]
public class NumersFixture
{
[Test]
public void AddTwoNumbers()
{
int a=1;
int b=2;
int sum=a+b;
Assert.AreEqual(sum,3);
}
[Test]
public void MultiplyTwoNumbers()
{
int a = 1;
int b = 2;
int product = a * b;
Assert.AreEqual(2, product);
}
}
不難看出兩個測試方法中有重複的代碼,如何去除重複的代碼呢?我們可以提取這些代碼到一個獨立的方法,然後标志這個方法為SetUp 屬性,這樣2個測試方法可以共享對操作數的初始化了,這裡是改動後的代碼:
using System;
using NUnit.Framework;
[TestFixture]
public class NumersFixture
{
private int a;
private int b;
[SetUp]
public void InitializeOperands()
{
a = 1;
b = 2;
}
[Test]
public void AddTwoNumbers()
{
int sum=a+b;
Assert.AreEqual(sum,3);
}
[Test]
public void MultiplyTwoNumbers()
{
int product = a * b;
Assert.AreEqual(2, product);
}
}
這樣NUnit将在執行每個測試前執行标記SetUp屬性的方法.在本例中就是執行InitializeOperands()方法.記住,這裡這個方法必須為public,不然就會有以下錯誤:Invalid Setup or TearDown method signature
4.TearDownAttribute
用來修飾方法。所屬的類必需已經标記為一個TestFixture。一個TestFixture可以僅有一個TearDown方法。如果有多個定義,TestFixture也會編譯成功,但是測試不會運作。被TearDownAttribute修飾的方法是每個測試方法被調用之後來執行的。
如:
using System;
using NUnit.Framework;
[TestFixture]
public class SuccessTests
{
[SetUp]
public void Init()
{ }
[TearDown]
public void Dispose()
{ }
[Test]
public void Add()
{ }
}
TearDown屬性是從任何的基類繼承而來。是以,如果基類已經定義了一個TearDown方法,那麼這個方法會在每個派生的類的測試方法之後調用。如果你打算在派生類裡加入更多的TearDown功能,你需要将方法标記為适當的屬性,然後調用基類方法。
5.TestFixtureSetUpAttribute
用來修飾方法。所屬的類必需已經标記為一個TestFixture。這些個屬性标記的方式在fixture任何測試執行之前完成。TestFixture可以僅有一個TestFixtureSetUp方法。如果定義了多個,TestFixture可以成功編譯,但是測試不會被執行。
6.TestFixtureTearDownAttribute
用來修飾方法。所屬的類必需已經标記為一個TestFixture。這些個屬性标記的方式在fixture任何測試執行之後完成。TestFixture可以僅有一個TestFixtureTearDownAttribute方法。如果定義了多個,TestFixture可以成功編譯,但是測試不會被執行。
如:
namespace NUnit.Tests
{
using System;
using NUnit.Framework;
[TestFixture]
public class SuccessTests
{
[TestFixtureSetUp]
public void Init()
{ }
[TestFixtureTearDown]
public void Dispose()
{ }
[Test]
public void Add()
{ }
}
}
SetUp/TearDown方法提供達到測試隔離性的目的.SetUp確定共享的資源在每個測試運作前正确初始化,TearDown確定沒有運作測試産生的遺留副作用. TestFixtureSetUp/TestFixtureTearDown同樣提供相同的目的,但是卻在test fixture範圍裡
我們寫一個簡單的測試來說明什麼方法調用了,怎麼合适調用
using System;
using NUnit.Framework;
[TestFixture]
public class LifeCycleContractFixture
{
[TestFixtureSetUp]
public void FixtureSetUp()
{
Console.Out.WriteLine("FixtureSetUp");
}
[TestFixtureTearDown]
public void FixtureTearDown()
{
Console.Out.WriteLine("FixtureTearDown");
}
[SetUp]
public void SetUp()
{
Console.Out.WriteLine("SetUp");
}
[TearDown]
public void TearDown()
{
Console.Out.WriteLine("TearDown");
}
[Test]
public void Test1()
{
Console.Out.WriteLine("Test 1");
}
[Test]
public void Test2()
{
Console.Out.WriteLine("Test 2");
}
}
運作結果:
FixtureSetUp
SetUp
Test 1
TearDown
SetUp
Test 2
TearDown
FixtureTearDown
7.ExpectedExceptionAttribute
修飾方法,用來測試一個方法是否抛出了指定的異常。本屬性有兩種重載方式。第一種是一個Type,此Type為期望的異常的精确類型。 第二種是一個期望的異常全名的字元串。
在執行測試時,如果它抛出了指定的異常,那麼測試通過。如果抛出一個不同的異常,測試就失敗。如果抛出了一個由期望異常繼承而來的異常,這也是成功的。
using System;
using NUnit.Framework;
[TestFixture]
public class SuccessTests
{
[Test]
[ExpectedException(typeof(InvalidOperationException))]
public void ExpectAnExceptionByType()
{ }
[Test]
[ExpectedException("System.InvalidOperationException")]
public void ExpectAnExceptionByName()
{ }
}
8.CategoryAttribute
修飾方法或修飾類。用來把測試分組,可以使用NUnit的Categories頁籤選擇要測試的組,或排除一些組。
對類分組:
using System;
using NUnit.Framework;
[TestFixture]
[Category("LongRunning")]
public class LongRunningTests
{
// ...
}
對方法分組:
using System;
using NUnit.Framework;
[TestFixture]
public class SuccessTests
{
[Test]
[Category("Long")]
public void VeryLongTest()
{ }
}
下面我們編寫三個測試方法,并分為兩組“Compare”和“TestNull”,在NUnit中測試“Compare”組的内容:
第一步:點選左側的“Categories”頁籤
第二步:在“Available Categories”清單框中選擇“Compare”,點選“Add”按鈕
第三步:點選左側“Test”頁籤,點選樹狀目錄中的“Class1”,點選“Run”按鈕
我們發現隻有TestTowInstanceName和TestTowInstanceEqual兩個測試方法運作了,而TestNull沒有運作,是以TestNull不是“Compare”組的。
《圖3》
《圖4》
9.ExplicitAttribute
用來修飾類或方法。Explicit屬性會忽略一個測試或測試Fixture,直到它被顯式的選擇運作。。如果test和test fixture在執行的過程中被發現,就忽略他們。是以,這樣一來進度條顯示為黃色,因為有test或test fixture忽略了。
修飾類:
using System;
using NUnit.Framework;
[TestFixture, Explicit]
public class ExplicitTests
{
// ...
}
修飾方法:
using System;
using NUnit.Framework;
[TestFixture]
public class SuccessTests
{
[Test, Explicit]
public void ExplicitTest()
{ }
}
10.IgnoreAttribute
用來修飾類或方法。由于種種原因,有一些測試我們不想運作.當然,這些原因可能包括你認為這個測試還沒有完成,這個測試正在重構之中,這個測試的需求不是太明确.但你有不想破壞測試,不然進度條可是紅色的喲.怎麼辦?使用Ignore屬性.你可以保持測試,但又不運作它們。
這個特性用來暫時不運作一個測試或fixture。比起注釋掉測試或重命名方法,這是一個比較好的機制,因為測試會和餘下的代碼一起編譯,而且在運作時有一個不會運作測試的标記,這樣保證不會忘記測試。
修飾類:
using System;
using NUnit.Framework;
[TestFixture]
[Ignore("Ignore a fixture")]
public class SuccessTests
{
// ...
}
修飾方法
using System;
using NUnit.Framework;
[TestFixture]
public class SuccessTests
{
[Test]
[Ignore("Ignore a test")]
public void IgnoredTest()
{ }
}
五:常用斷言
在NUnit中,斷言是單元測試的核心。NUnit提供了一組豐富的斷言,這些斷言是Assert類的靜态方法。如果一個斷言失敗,方法的調用不會傳回值,并且會報告一個錯誤。如果一個測試包含多個斷言,那些緊跟失敗斷言的斷言都不會執行,是以,通常每個測試方法最好隻有一個斷言。
Assert類提供了最常用的斷言。我們将Assert方法按如下分組:
a.同等(Equality)斷言
b.一緻性(Identity)斷言
c.比較(Comparison)斷言
d.類型(Type)斷言
e.條件(Condition)測試
f.工具(Utility)方法
1.同等斷言
主要包括Assert.AreEqual()、Assert.AreNotEqual()和Assert.IsNaN()
前兩個方法測試2個參數是否相等。重載的方法支援普通的值類型。
Assert.AreEqual( int expected, int actual );
Assert.AreEqual( int expected, int actual, string message );
Assert.AreEqual( int expected, int actual, string message,params object[] parms );
Assert.AreEqual( uint expected, uint actual );
Assert.AreEqual( uint expected, uint actual, string message );
Assert.AreEqual( uint expected, uint actual, string message,params object[] parms );
Assert.AreEqual( decimal expected, decimal actual );
Assert.AreEqual( decimal expected, decimal actual, string message );
Assert.AreEqual( decimal expected, decimal actual, string message,params object[] parms );
Assert.AreEqual( float expected, float actual, float tolerance );
Assert.AreEqual( float expected, float actual, float tolerance,string message );
Assert.AreEqual( float expected, float actual, float tolerance,string message, params object[] parms );
Assert.AreEqual( double expected, double actual, double tolerance );
Assert.AreEqual( double expected, double actual, double tolerance,string message );
Assert.AreEqual( double expected, double actual, double tolerance,string message, params object[] parms );
Assert.AreEqual( object expected, object actual );
Assert.AreEqual( object expected, object actual, string message );
Assert.AreEqual( object expected, object actual, string message,params object[] parms );
Assert.AreNotEqual( int expected, int actual );
Assert.AreNotEqual( int expected, int actual, string message );
Assert.AreNotEqual( int expected, int actual, string message,params object[] parms );
Assert.AreNotEqual( uint expected, uint actual );
Assert.AreNotEqual( uint expected, uint actual, string message );
Assert.AreNotEqual( uint expected, uint actual, string message,params object[] parms );
Assert.AreNotEqual( decimal expected, decimal actual );
Assert.AreNotEqual( decimal expected, decimal actual, string message );
Assert.AreNotEqual( decimal expected, decimal actual, string message,params object[] parms );
Assert.AreNotEqual( float expected, float actual );
Assert.AreNotEqual( float expected, float actual, string message );
Assert.AreNotEqual( float expected, float actual, string message,params object[] parms );
Assert.AreNotEqual( double expected, double actual );
Assert.AreNotEqual( double expected, double actual, string message );
Assert.AreNotEqual( double expected, double actual, string message,params object[] parms );
Assert.AreNotEqual( object expected, object actual );
Assert.AreNotEqual( object expected, object actual, string message );
Assert.AreNotEqual( object expected, object actual, string message,params object[] parms );
不同類型的數值可以按期望的那樣進行比較。下面的斷言會成功:
Assert.AreEqual( 5, 5.0 );
float型和double型的數值通常使用一個附加參數來進行比較,這個參數代表一個誤差,在這個誤差範圍内,它們視為相等。
如果2個一維數組有相同的長度,而且相應的數組元素也相等,那麼通過調用Assert.AreEqual方法,這2個數組視為相等。
注: 多元數組,嵌套數組(數組的數組),以及其他集合類型,例如ArrayList目前還不支援。
2.一緻性斷言
Assert.AreSame()方法、Assert.AreNotSame方法。這兩個方法主要判斷兩個參數引用的是否是同一個對象。
Assert.AreSame( object expected, object actual );
Assert.AreSame( object expected, object actual, string message );
Assert.AreSame( object expected, object actual, string message,params object[] parms );
Assert.AreNotSame( object expected, object actual );
Assert.AreNotSame( object expected, object actual, string message );
Assert.AreNotSame( object expected, object actual, string message,params object[] parms );
Assert.Contains()方法用來測試在一個數組或清單裡是否包含該對象。
Assert.Contains( object anObject, IList collection );
Assert.Contains( object anObject, IList collection,string message );
Assert.Contains( object anObject, IList collection,string message, params object[] parms );
3.比較斷言
Assert.Greater():測試一個對象是否大于另外一個。
Assert.Less():測試一個對象是否于小另外一個。
Assert.Greater( int arg1, int arg2 );
Assert.Greater( int arg1, int arg2, string message );
Assert.Greater( int arg1, int arg2, string message,object[] parms );
Assert.Greater( uint arg1, uint arg2 );
Assert.Greater( uint arg1, uint arg2, string message );
Assert.Greater( uint arg1, uint arg2, string message,object[] parms );
Assert.Greater( decimal arg1, decimal arg2 );
Assert.Greater( decimal arg1, decimal arg2, string message );
Assert.Greater( decimal arg1, decimal arg2, string message,object[] parms );
Assert.Greater( double arg1, double arg2 );
Assert.Greater( double arg1, double arg2, string message );
Assert.Greater( double arg1, double arg2, string message,object[] parms );
Assert.Greater( double arg1, double arg2 );
Assert.Greater( double arg1, double arg2, string message );
Assert.Greater( double arg1, double arg2, string message,object[] parms );
Assert.Greater( float arg1, float arg2 );
Assert.Greater( float arg1, float arg2, string message );
Assert.Greater( float arg1, float arg2, string message,object[] parms );
Assert.Greater( IComparable arg1, IComparable arg2 );
Assert.Greater( IComparable arg1, IComparable arg2, string message );
Assert.Greater( IComparable arg1, IComparable arg2, string message,object[] parms );
Assert.Less( int arg1, int arg2 );
Assert.Less( int arg1, int arg2, string message );
Assert.Less( int arg1, int arg2, string message,object[] parms );
Assert.Less( uint arg1, uint arg2 );
Assert.Less( uint arg1, uint arg2, string message );
Assert.Less( uint arg1, uint arg2, string message,object[] parms );
Assert.Less( decimal arg1, decimal arg2 );
Assert.Less( decimal arg1, decimal arg2, string message );
Assert.Less( decimal arg1, decimal arg2, string message,object[] parms );
Assert.Less( double arg1, double arg2 );
Assert.Less( double arg1, double arg2, string message );
Assert.Less( double arg1, double arg2, string message,object[] parms );
Assert.Less( float arg1, float arg2 );
Assert.Less( float arg1, float arg2, string message );
Assert.Less( float arg1, float arg2, string message,object[] parms );
Assert.Less( IComparable arg1, IComparable arg2 );
Assert.Less( IComparable arg1, IComparable arg2, string message );
Assert.Less( IComparable arg1, IComparable arg2, string message,object[] parms );
4.類型斷言
Assert.IsInstanceOfType():判斷一個對象的類型是否是期望的類型
Assert.IsNotInstanceOfType():判斷一個對象的類型是否不是期望的類型
Assert.IsAssignableFrom():判斷一個對象的類型是否屬于某種類型
Assert.IsNotAssignableFrom():判斷一個對象的類型是否不屬于某種類型
Assert.IsInstanceOfType( Type expected, object actual );
Assert.IsInstanceOfType( Type expected, object actual,string message );
Assert.IsInstanceOfType( Type expected, object actual,string message, params object[] parms );
Assert.IsNotInstanceOfType( Type expected, object actual );
Assert.IsNotInstanceOfType( Type expected, object actual,string message );
Assert.IsNotInstanceOfType( Type expected, object actual,string message, params object[] parms );
Assert.IsAssignableFrom( Type expected, object actual );
Assert.IsAssignableFrom( Type expected, object actual,string message );
Assert.IsAssignableFrom( Type expected, object actual,string message, params object[] parms );
Assert.IsNotAssignableFrom( Type expected, object actual );
Assert.IsNotAssignableFrom( Type expected, object actual,string message );
Assert.IsNotAssignableFrom( Type expected, object actual,string message, params object[] parms );
5.條件測試斷言
這些方法測試并把測試的值作為他們的第一個參數以及把一個消息作為第二個參數,第二個參數是可選的。本文提供了下面的方法:
Assert.IsTrue( bool condition );
Assert.IsTrue( bool condition, string message );
Assert.IsTrue( bool condition, string message, object[] parms );
Assert.IsFalse( bool condition);
Assert.IsFalse( bool condition, string message );
Assert.IsFalse( bool condition, string message, object[] parms );
Assert.IsNull( object anObject );
Assert.IsNull( object anObject, string message );
Assert.IsNull( object anObject, string message, object[] parms );
Assert.IsNotNull( object anObject );
Assert.IsNotNull( object anObject, string message );
Assert.IsNotNull( object anObject, string message, object[] parms );
Assert.IsNaN( double aDouble );
Assert.IsNaN( double aDouble, string message );
Assert.IsNaN( double aDouble, string message, object[] parms );
Assert.IsEmpty( string aString );
Assert.IsEmpty( string aString, string message );
Assert.IsEmpty( string aString, string message,params object[] args );
Assert.IsNotEmpty( string aString );
Assert.IsNotEmpty( string aString, string message );
Assert.IsNotEmpty( string aString, string message,params object[] args );
Assert.IsEmpty( ICollection collection );
Assert.IsEmpty( ICollection collection, string message );
Assert.IsEmpty( ICollection collection, string message,params object[] args );
Assert.IsNotEmpty( ICollection collection );
Assert.IsNotEmpty( ICollection collection, string message );
Assert.IsNotEmpty( ICollection collection, string message,params object[] args );
6.StringAssert斷言
StringAssert.Contains( string expected, string actual );
StringAssert.Contains( string expected, string actual,string message );
StringAssert.Contains( string expected, string actual,string message, params object[] args );
StringAssert.StartsWith( string expected, string actual );
StringAssert.StartsWith( string expected, string actual, string message );
StringAssert.StartsWith( string expected, string actual,string message, params object[] args );
StringAssert.EndsWith( string expected, string actual );
StringAssert.EndsWith( string expected, string actual,string message );
StringAssert.EndsWith( string expected, string actual,string message, params object[] args );
StringAssert.AreEqualIgnoringCase( string expected, string actual );
StringAssert.AreEqualIgnoringCase( string expected, string actual,string message );
StringAssert.AreEqualIgnoringCase( string expected, string actual,string message params object[] args );
7.實用方法
Fail()和Ignore(),是為了讓我們對測試過程有更多的控制:
Assert.Fail();
Assert.Fail( string message );
Assert.Fail( string message, object[] parms );
Assert.Ignore();
Assert.Ignore( string message );
Assert.Ignore( string message, object[] parms );
Assert.Fail方法為你提供了建立一個失敗測試的能力:
public void AssertStringContains( string expected, string actual )
{
AssertStringContains( expected, actual, string.Empty );
}
public void AssertStringContains( string expected, string actual,string message )
{
if ( actual.IndexOf( expected ) < 0 )
Assert.Fail( message );
}
Assert.Ignore方法為你提供在運作時動态忽略一個測試或者一個測試套件(suite)的能力。它可以在一個測試,一個setup或fixture setup的方法中調用。我們建議你隻在無效的案例中使用。它也為更多擴充的測試包含或排斥提供了目錄能力,或者你可以簡單将不同情況下運作的測試運作分解到不同的程式集。