单元测试框架NUnit 之 assertions 断言
断言是任何一个xunit框架的核心,nunit中的Assert类,它提供一系列丰富的静态方法来供我们调用。如果一个断言失败,这个方法不会返回但是会报告一个错误,那么断言之后的代码就不会被执行,因此,最好在一个测试中包含一个断言。
经典模型
在Nunit2.4以前,使用经典模型:每一个断言调用一个单独的方法。
如:
// 测试两者是否相等 Assert.AreEqual( int expected, int actual ); // 测试两个对象是否是同一个引用 Assert.AreSame( object expected, object actual );
基于约束(Constraint-based)模型
从Nunit2.4以后,一个“基于约束”(Constraint-based)的模型被引入,所有的断言都使用Assert类的一个单独方法,传入一个约束对象来决定方法的执行方式。所有的断言都是在基于约束的模型上实现的,以前的经典方式也在这种模型基础上重新实现了。
如:
// 这样测试两者是否相等 Assert.That( myString, Is.EqualTo("Hello") ); // 这样也可以测试两者是否相等 Assert.That( myString, new EqualConstraint("Hello") );
第二个参数就是所谓的约束对象,而Assert.That就是那个单独的方法,上面的两句最终实现是一样的,第一句用语法帮助类实现了EqualConstraint。
所有的约束都必须继承IConstraint,他支持对一个真实值执行测试并生成适当的信息。Nunit提供了一些像AreEqual的约束类来供我们直接使用,来实现和经典模式调用相同的功能,当然这些类都可以直接调用或通过语法帮助来实现。关于约束的更详细的内容,将在以后详细总结。
另外如果你的测试类继承自AssertionHelper,还有一个Expect()的方法可以替代Assert.That方法来使用。
当然,我们仍然可以不直接接触约束类,像经典模型那样来使用断言,nunit提供了11类直接调用的断言,其中的任何一个断言方法都有数量不等的重载版本:
1,Equality Asserts
这些方法是用来测试两个参数是否相等的,它的重载版本可用来比较所有的值类型,因此不用转换而直接来使用。
Assert.AreEqual( int expected, int actual ); Assert.AreNotEqual( int expected, int actual );
不同类型的数值的比较,会调用两个object的重载版本,因些像
Assert.AreEqual( 5, 5.0 );
这样的断言是成功的。
float 和 double 类型的数据比较,一般都要一个另外的参数来指定误差,在这个范围内的断言都是成功的。从2.4.4版本之后GlobalSettings.DefaultFloatingPointTolerance被用来指示这个误差。如果这个值没设置或在早期的版本中,比较的都是实际值。
另外,从2.2以后,NUint已经可能用来一维的数组了。从2.4开始,多维数组、交错数组和集合也被支持,如果有相同的维度和大小,并且各个元素也相等,它们就是相等的。
2,Identity Asserts
Assert.AreSame 和 Assert.AreNotSame 被用来测试两个对象对否是同一个引用。
Assert.AreSame( object expected, object actual ); Assert.AreNotSame( object expected, object actual );
Assert.Contains 被用来测试一个对象是否包含在数据或集合中。
Assert.Contains( object anObject, IList collection );
3,Condition Asserts
Assert.IsTrue( bool condition );
Assert.True( bool condition );
Assert.IsFalse( bool condition); Assert.False( bool condition); Assert.IsNull( object anObject ); Assert.Null( object anObject ); Assert.IsNotNull( object anObject ); Assert.NotNull( object anObject ); Assert.IsNaN( double aDouble ); Assert.IsEmpty( string aString ); Assert.IsNotEmpty( string aString ); Assert.IsEmpty( ICollection collection ); Assert.IsNotEmpty( ICollection collection );
我们可以看到方法的名字就是我们的逻辑,其中True,IsTrue类似的都有两个版本,其中IsTrue是用来兼容以前的版本的。
4,Comparisons Asserts
从2.2.4版本以后才有的。它提供了四类方法分别用于大于、大于或等于、小于、小于或等于的判断。每类方法都有数量庞大的方法重载.
Assert.Greater( int arg1, int arg2 ); Assert.GreaterOrEqual( int arg1, int arg2 ); Assert.Less( int arg1, int arg2 ); Assert.LessOrEqual( int arg1, int arg2 );
5,Type Asserts
这些方法可以让我们对对象的类型进行测试,从2.2.3版本之后支持。
Assert.IsInstanceOfType( Type expected, object actual ); Assert.IsNotInstanceOfType( Type expected, object actual ); Assert.IsAssignableFrom( Type expected, object actual ); Assert.IsNotAssignableFrom( Type expected, object actual );
从2.5版本之后,在Nunit .net2.0开始支持泛型。
Assert.IsInstanceOf<T>( object actual ); Assert.IsNotInstanceOf<T>( object actual ); Assert.IsAssignableFrom<T>( object actual ); Assert.IsNotAssignableFrom<T>( object actual );
6,Exception Asserts
Exception Assert.Throws( Type expectedExceptionType, TestDelegate code ); T Assert.Throws<T>( TestDelegate code ); void Assert.DoesNotThrow( TestDelegate code ); Exception Assert.Catch( TestDelegate code ); T Assert.Catch<T>( TestDelegate code );
Assert.Throws 不是用来比较值的,而运行做为委托的一段代码,为了确定他是否抛出一个特定的异常。另外,它还有可以再用一个参数来指定期望异常为特定异常或类型。Assert.DoesNotThrow则核实委托不抛出异常。
Assert.Catch 和 Assert.Throws类似,但是它允许抛出的异常继承自传入的期望类型。
上面说到的 TestDelegate 是一个 void TestDelegate()的代理,在2.0中可以是匿名的,3。0中可以是lambda表达式。
下面的例子演示了相同测试的不同写法:
[TestFixture] public class AssertThrowsTests { [Test] public void Tests() { // .NET 1.x Assert.Throws( typeof(ArgumentException), new TestDelegate(MethodThatThrows) ); // .NET 2.0 Assert.Throws<ArgumentException>( MethodThatThrows() ); Assert.Throws<ArgumentException>( delegate { throw new ArgumentException(); } ); // Using C# 3.0 Assert.Throws<ArgumentException>( () => throw new ArgumentException(); } ); } void MethodThatThrows() { throw new ArgumentException(); }
下面的例子演示了对抛出的异常进行核实,两种写法可以达到同一个效果。
[TestFixture] public class UsingReturnValue { [Test] public void TestException() { // first way MyException ex = Assert.Throws<MyException>( delegate { throw new MyException( "message", 42 ); } ); Assert.That( ex.Message, Is.EqualTo( "message" ) ); Assert.That( ex.MyParam, Is.EqualTo( 42 ) ); // other different type but same test way Assert.Throws( Is.Typeof<MyException>() .And.Message.EqualTo( "message" ) .And.Property( "MyParam" ).EqualTo( 42 ), delegate { throw new MyException( "message", 42 ); } ); } }
7,Utility Methods
Assert.Pass(); Assert.Pass( string message ); Assert.Pass( string message, object[] parms ); 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.Inconclusive(); Assert.Inconclusive( string message ); Assert.Inconclusive( string message, object[] parms );
这些方法让我们能直接控制测试的进程,其中Assert.Pass 让你直接通过测试来结束这次的测试:这会导致一个异常产生,是高效的让测试返回的方法。除此之外,你也可以提供一个消息,让我们可以直观看出原因。
Assert.Fail 可以让测试失败为结果来结束本次测试。
Assert.Ignore 让我们有能力动态的忽略某个测试或条件。它可以在test,setup,fixture setup方法中调用。建议只在单独的条件内调用,因为Nunit的分类机制提供更广泛的如包含、排除等,你可以更简单的选择让测试运行在不同的场合或者程序集。
Assert.Inconclusive 的作用是指出现有条件下,测试不能完成:因为它不能证明断言是成功或失败。
这几个方法一样,即使把它放到一个独立的方法中,然后再调用这个方法,它仍然会被激发来结束测试。
例如你可以这样用:
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 ); }
8,StringAssert
StringAssert.Contains( string expected, string actual ); StringAssert.StartsWith( string expected, string actual ); StringAssert.EndsWith( string expected, string actual ); StringAssert.AreEqualIgnoringCase( string expected, string actual ); StringAssert.IsMatch( string regexPattern, string actual );
它提供的几个方法,在验证字符串时非常有用。从2.2.3版本才有。
9,CollectionAssert
CollectionAssert.AllItemsAreInstancesOfType( IEnumerable collection, Type expectedType ); CollectionAssert.AllItemsAreNotNull( IEnumerable collection ); CollectionAssert.AllItemsAreUnique( IEnumerable collection ); CollectionAssert.AreEqual( IEnumerable expected, IEnumerable actual ); CollectionAssert.AreEquivalent( IEnumerable expected, IEnumerable actual); CollectionAssert.AreNotEqual( IEnumerable expected, IEnumerable actual ); CollectionAssert.AreNotEquivalent( IEnumerable expected, IEnumerable actual ); CollectionAssert.Contains( IEnumerable expected, object actual ); CollectionAssert.DoesNotContain( IEnumerable expected, object actual ); CollectionAssert.IsSubsetOf( IEnumerable subset, IEnumerable superset ); CollectionAssert.IsSubsetOf( IEnumerable subset, IEnumerable superset, string message ); CollectionAssert.IsNotSubsetOf( IEnumerable subset, IEnumerable superset); CollectionAssert.IsEmpty( IEnumerable collection ); CollectionAssert.IsNotEmpty( IEnumerable collection );
CollectionAssert类提供的这些方法主要用于检查集合或者集合内容或者来比较两个集合。
AreEqual 的重载方法要求两个集合的对应元素要相等,而 AreEquivalent 要求两个集合的内容相等,而不要求顺序。两者元素的比较都应用默认的相等比较。
2.4.6之前要求两都必须是真正的集合,之后只要是继承自IEnumerable接口即可。2.5之后,也提供以下方法可供使用。
CollectionAssert.IsOrdered( IEnumerable collection ); CollectionAssert.IsOrdered( IEnumerable collection, IComparer comparer );
10,FileAssert
FileAssert.AreEqual( Stream expected, Stream actual ); FileAssert.AreNotEqual( Stream expected, Stream actual );
文件的比较,用来比较两个文件,可以Streams, FileInfos 或者路径的形式。从2.4版本之后开始支持。
11,DirectoryAssert
这个类提供的方法让我们可以对系统的文件目录进行断言,可以以DirectoryInfos 或者路径 的形式。
DirectoryAssert.AreEqual( DirectoryInfo expected, DirectoryInfo actual ); DirectoryAssert.AreNotEqual( DirectoryInfo expected, DirectoryInfo actual );
DirectoryAssert.AreEqual() 和 DirectoryAssert.AreNotEqual()用来比较两个目录是否相等:相同的全名,属性Attributes, 创建时间CreationTime 和最后访问时间LastAccessTime. 两个包含相等文件的不同目录是不会被认为相同的。
DirectoryAssert.IsEmpty( DirectoryInfo directory ); DirectoryAssert.IsNotEmpty( DirectoryInfo directory );
这些被用来测试目录是否为空。
DirectoryAssert.IsWithin( DirectoryInfo expected, DirectoryInfo actual ); DirectoryAssert.IsNotWithin( DirectoryInfo expected, DirectoryInfo actual );
这些方法用来测试第二个目录是不是第一个目录的子(孙子等)目录。
断言介绍完毕,下文将总结约束、特性和扩展。