
11.4 测试环境
在编写测试用例时,测试引擎提供了各种辅助操作,包括管理数据集,这样就可以用大量的数据运行测试用例,并且为测试用例输出能够辅助调试的各种其他信息。可以使用在测试类中生成并传送给AssemblyInitialize和ClassInitialize方法的TestContext对象来实现这些功能。下面的代码演示了捕获TestContext对象值的一种方式,以便在测试中使用它:
VB
Private Shared testContextInstance As TestContext <ClassInitialize> _ Public Shared Sub MyClassInitialize(testContext As TestContext) testContextInstance = testContext End Sub
C#
private static TestContext testContextInstance; [ClassInitialize()] public static void MyClassInitialize(TestContext testContext) { testContextInstance = testContext; }
11.4.1 数据
本章11.1一节编写的CurrentStatus_NullPaidUpToDate_TemporaryStatus方法只能测试通过CurrentStatus属性的一条路径。为充分测试这个属性,还需要编写其他方法,每个方法都带有自己的设置和断言。然而,这种处理非常机械,如果开发人员修改了CurrentStatus属性的结构,那么测试人员就不得不修改这些语句。为了解决这个问题,可以为CurrentStatus_NullPaidUpToDate_ TemporaryStatus方法提供一个DataSource,每一行数据测试通过该属性的一个路径。要为该方法添加数据,可以遵循下面的步骤。
(1)创建一个本地数据库文件(.MDF文件)和数据库表来存储各种测试数据(详情请参见第28章)。在本例中,创建一个名为LoadTest的数据库以及一个名为Subscription_CurrentStatus的表。Subscription_CurrentStatus表包含了bigint标识列Id、可以为空的datatime列PaidUp,以及nvarchar(20)类型的Status列。
(2)在表中添加能够覆盖代码中所有路径的适当数据值。为CurrentStatus属性设计的测试数据如图11-9所示。

图11-9
(3)为该测试用例添加DataSource特性。测试引擎使用这个特性从指定的表中加载适当的值。然后通过TestContext对象将该数据提供给测试用例。
如果使用LocalDB数据库或Excel文件,则还需要添加DeploymentItem特性。如果测试程序集部署到另一个位置上,则该特性可以确保复制该数据源。
(4)修改测试用例,使其从TestContext对象获取数据,并使用这些数据驱动整个测试用例。修改后的CurrentStatus_NullPaidUpToDate_TemporaryStatus方法如下所示。
VB
<DataSource("System.Data.SqlClient", _ "server=.\\SQLExpress;" & _ "AttachDBFilename=|DataDirectory|\\LoadTest.mdf;" & _ "Integrated Security=True", _ "Subscription_CurrentStatus", DataAccessMethod.Sequential)> _ <TestMethod()>_ Public Sub CurrentStatus_NullPaidUpToDate_TemporaryStatus() Dim target As Subscription = New Subscription If Not IsDBNull(testContextInstance.DataRow.Item("PaidUp"))Then target.PaidUpTo = CType(testContextInstance.DataRow.Item("PaidUp"), Date) End If Dim val As Subscription.Status = _ CType([Enum].Parse(GetType(Subscription.Status), _ CStr(testContextInstance.DataRow.Item("Status"))), Subscription.Status) Assert.AreEqual(val, target.CurrentStatus, _ "Subscription.CurrentStatus was not set correctly.") End Sub
C#
[DataSource("System.Data.SqlClient", "server=.\\SQLExpress;" + "AttachDBFilename=|DataDirectory|\\LoadTest.mdf;" + "Integrated Security=True", "Subscription_CurrentStatus", DataAccessMethod.Sequential)] [TestMethod()] public void CurrentStatus_NullPaidUpToDate_TemporaryStatus() { var target = new Subscription(); var date = testContextInstance.DataRow["PaidUp"] as DateTime?; if (date != null) { target.PaidUpTo = date; } var val = Enum.Parse(typeof(Subscription.Status), testContextInstance.DataRow["Status"] as string); Assert.AreEqual(val, target.CurrentStatus, "Subscription.CurrentStatus was not set correctly."); }
这个示例代码假定SQL Server Express实例运行在.\ SQLExpress下。如果SQL Server实例的主机名不同,就需要使用该主机名作为DataSource连接字符串中的服务器属性。另外,根据用于运行SQL Server和Visual Studio的身份,第一次运行测试时,可能有一些权限问题。具体而言,运行的身份必须拥有LoadTest.mdf文件的读写权限。运行Visual Studio的身份需要拥有SQL Server实例的管理员权限(以附加LoadTest.mdf)。
执行这个测试用例时,CurrentStatus_NullPaidUpToDate_TemporaryStatus方法会运行4次(每次使用数据库表中的一行数据)。每次执行该方法时,都会从数据库表中检索一个DataRow对象,测试方法通过TestContext.DataRow属性对这些数据进行访问。如果CurrentStatus属性中的逻辑改变了,那么可以在Subscription_CurrentStatus表中添加一行,测试已经创建的所有代码路径。
在继续介绍之前,最后看一眼应用于CurrentStatus_NullPaidUpToDate_TemporaryStatus方法的DataSource特性。该特性有4个参数,前3个参数用于判断需要提取哪个DataTable。最后一个参数是DataAccessMethod枚举,它指定了按照什么顺序从DataTable中返回行。默认情况下,该值为Sequential,但也可以把它指定为Random,从而实现每次运行测试时都能按照不同的顺序提取数据。如果数据代表的是最终用户的数据,并且对数据的处理顺序没有任何依赖关系,那么这种处理方式就非常必要。
数据驱动的测试不仅限于数据库表,还可以通过Excel电子表格或逗号分隔的值(Comma-Separated Values,CSV)文件来驱动。
11.4.2 编写测试输出
编写单元测试实际上就是自动化应用程序的测试过程。因此,这些测试用例可以在生成过程中执行,甚至在远程计算机上也可以执行。这就意味着常规的输出窗口(如控制台)并不适合输出与测试相关的信息。显然,我们不希望测试的相关信息与应用程序生成的调试或者跟踪信息交叉混合在一起。为此,可以使用一个专门通道,输出与测试相关的信息,把它与测试结果显示在一起。
TestContext对象的WriteLine方法接受一个String类型的参数和一系列String.Format类型的参数,可以使用这些参数输出与特定测试的结果相关的信息。例如,为CurrentStatusDataTest方法添加下面的代码,输出测试结果的额外信息。
VB
TestContext.WriteLine("No exceptions thrown for test id {0}", _ CInt(Me.TestContext.DataRow.Item(0)))
C#
TestContext.WriteLine("No exceptions thrown for test id {0}", this.TestContext.DataRow[0]);
尽管应该使用TestContext.WriteLine方法捕获测试执行的细节,但Visual Studio测试工具会收集输出到标准错误和标准输出流中的所有信息,并把这些数据添加到测试结果中。
测试运行完成后,就会显示Test Explorer窗口,列出在测试运行过程中执行的所有测试用例及其结果。Test Explorer窗口如图11-10所示,其中列出了运行已完成(已通过)的单元测试。

图11-10
在图11-10中可以注意到,有一个名为Output的链接。单击此链接,就可以直接转到测试的Test Output窗口。在Test Output窗口中,可以看到如图11-11所示的Standard Output部分,其中包含来自于添加到测试方法的WriteLine方法的输出。尽管只添加了一行到测试方法中,但WriteLine方法对数据库表中的每一行数据都执行了一次。由于每个数据驱动的测试都有自己的测试结果,因此该行出现在每个测试中。

图11-11