
11.2 指定判断条件
到目前为止,本章介绍了测试环境的结构以及测试用例是如何嵌套在测试项目的测试类中的。现在,研究一下测试用例的结构,查看测试用例的通过或失败是如何实现的。在创建测试用例以后,在测试用例的末端可以看到一条添加的Assert.Inconclusive语句,指明测试尚未完成。
单元测试的设计原则是在测试开始时,系统、组件或者对象处于一个已知的状态,然后运行方法、修改属性或者触发事件。在测试的最终阶段,需要验证系统、组件或者对象是否处于正确的状态。某些情况下,还需要验证方法或者属性返回的结果是否正确。此时就需要指定相应的判断条件。如果条件为假,则测试系统返回该结果并结束测试用例。条件是通过Assert类指定的。此外,StringAssert类和CollectionAssert类分别用于在处理String对象和对象集合时指定其他条件。
11.2.1 Assert类
UnitTesting名称空间中的Assert类(请不要把它与System.Diagnostics名称空间中的Debug.Assert或Trace.Assert方法混淆在一起)是用于对测试用例进行条件判断的主要类。其基本的断言格式如下所示。
VB
Assert.IsTrue(variableToTest, "Output message if this fails")
C#
Assert.IsTrue(variableToTest, "Output message if this fails");
第一个参数就是要测试的条件。如果条件为真,则测试用例将继续执行。否则,测试用例就会输出一条消息,并返回失败的结果。
该语句有多种重载形式,它们有的可以省略输出消息,有的可以提供String格式的参数。很多情况下,不是只测试一个条件是否为真,而是要在测试用例中进行各种其他形式的测试,此时可以使用下面的这些方法。
●IsFalse:测试一个为负或者假的条件。
●AreEqual:测试两个参数是否有相同的值。
●AreSame:测试两个参数是否引用同一个对象。
●IsInstanceOfType:判断参数是不是某个特定类型的实例。
●IsNull:测试一个参数是否为空。
上面并不是完整的列表,还有其他的方法,并且这些列出的方法都有否定等价方法。此外,许多方法都有多种重载方式,可以通过一些不同的方式调用它们。
11.2.2 StringAssert类
StringAssert类提供的每一个功能都可以通过使用Assert类判断一个或多个条件来实现。但是, StringAssert类建立的是String断言的条件,不仅简化了测试用例的代码,还减少了测试某些条件所需的工作。StringAssert类提供的断言包括如下。
●Contains:测试一个字符串中是否包含另一个字符串。
●DoesNotMatch:测试一个字符串是否不匹配一个正则表达式。
●EndsWith:测试一个字符串是否以指定的字符串结尾。
●Matches:测试一个字符串是否匹配一个正则表达式。
●StartsWith:测试一个字符串是否以指定的字符串开头。
11.2.3 CollectionAssert类
类似于StringAssert类,CollectionAssert类是一个用于对项集合进行断言的辅助类。例如,可以进行下面几种断言。
●AllItemsAreNotNull:测试集合中的项是否没有空引用。
●AllItemsAreUnique:测试集合中是否有重复的项。
●Contains:测试一个集合中是否包含指定的对象。
●IsSubsetOf:测试一个集合是不是指定集合的子集。
11.2.4 ExpectedException特性
有时,测试用例所执行的代码路径会引起异常。应尽量避免编写抛出异常的代码,但有些情况下这种处理又是合理的。在测试用例中,除了在Try-Catch块中使用合适的断言测试是否抛出了异常以外,还可以把测试用例标记为ExpectedException特性。例如,下面的代码修改了CurrentStatus属性,如果PaidUp的日期在订阅日期(这里声明为一个常量)之前,就抛出异常。
VB
Public Const SubscriptionOpenedOn As Date = #1/1/2000# Public ReadOnly Property CurrentStatus As Status Get If Not Me.PaidUpTo.HasValue Then Return Status.Temporary If Me.PaidUpTo > Now Then Return Status.Financial Else If Me.PaidUpTo >= Now.AddMonths(-3)Then Return Status.Unfinancial ElseIf Me.PaidUpTo > SubscriptionOpenedOn Then Return Status.Suspended Else Throw New ArgumentOutOfRangeException( _ "Paid up date is not valid as it is before the subscription opened.") End If End If End Get End Property
C#
public static readonly DateTime SubscriptionOpenedOn = new DateTime(2000, 1, 1); public Status CurrentStatus { get { if (this.PaidUpTo.HasValue == false) return Status.Temporary; if (this.PaidUpTo > DateTime.Today) return Status.Financial; else { if (this.PaidUpTo >= DateTime.Today.AddMonths(-3)) return Status.Unfinancial; else if (this.PaidUpTo >= SubscriptionOpenedOn) return Status.Suspended; else throw new ArgumentOutOfRangeException( "Paid up date is not valid as it is before the subscription opened"); } } }
按照与之前相同的方法,可以创建一个测试该代码路径的测试用例,如下面的示例所示。
VB
<TestMethod()> <ExpectedException(GetType(ArgumentOutOfRangeException), "Argument exception not raised for invalid PaidUp date.")> Public Sub CurrentStatusExceptionTest() Dim target As Subscription = New Subscription target.PaidUpTo = Subscription.SubscriptionOpenedOn.AddMonths(-1) Dim expected = Subscription.Status.Temporary Assert.AreEqual(expected, target.CurrentStatus, _ "This assertion should never actually be evaluated") End Sub
C#
[TestMethod()] [ExpectedException(typeof(ArgumentOutOfRangeException), "Argument Exception not raised for invalid PaidUp date.")] public void CurrentStatusExceptionTest() { Subscription target = new Subscription(); target.PaidUpTo = Subscription.SubscriptionOpenedOn.AddMonths(-1); var expected = Subscription.Status.Temporary; Assert.AreEqual(expected, target.CurrentStatus, "This assertion should never actually be evaluated"); }
ExceptedException特性不仅可以捕获由测试用例引起的每一个异常,还可以确保异常的类型匹配预期的类型。如果测试用例没有引起任何异常,那么这个特性就会导致测试失败。