Visual Studio 2015高级编程(第6版)
上QQ阅读APP看书,第一时间看更新

11.5 高级单元测试

前面学习了如何编写和执行单元测试。本节将继续学习如何为测试用例添加定制属性,以及如何使用同样的框架来测试私有方法和私有属性。

11.5.1 定制属性

借助测试框架为测试方法提供的各种测试特性,可以记录与测试用例相关的信息。可以在Properties窗口中对这些信息进行编辑,更新测试方法中相应的特性。有时需要指定自己定制的属性来驱动测试方法,这也可以使用Properties窗口进行设置。为此,在测试方法中添加TestProperty特性。例如,下面的代码为测试方法添加了两个特性,一个用于指定任意的日期,另一个用于指定期望的状态。这对于使用Test View窗口和Properties窗口进行的随机测试特别方便。

VB

      <TestMethod()>
      <TestProperty("SpecialDate", "1/1/2008")>
      <TestProperty("SpecialStatus", "Suspended")>
      Public Sub SpecialCurrentStatusTest()
          Dim target As New Subscription
          target.PaidUpTo = CType(Me.TestContext.Properties.Item("SpecialDate"), _
              Date)
          Dim val As Subscription.Status = _
              [Enum].Parse(GetType(Subscription.Status), _
              CStr(Me.TestContext.Properties.Item("SpecialStatus")))
          Assert.AreEqual(val, target.CurrentStatus, _
              "Correct status not set for Paidup date {0}", target.PaidUpTo)
      End Sub

C#

      [TestMethod]
      [TestProperty("SpecialDate", "1/1/2008")]
      [TestProperty("SpecialStatus", "Suspended")]
      public void SpecialCurrentStatusTest()
      {
          var target = new Subscription();
          target.PaidUpTo = this.TestContext.Properties["SpecialDate"] as DateTime?;
          var val = Enum.Parse(typeof(Subscription.Status),
              this.TestContext.Properties["SpecialStatus"] as string);
          Assert.AreEqual(val, target.CurrentStatus,
              "Correct status not set for Paidup date {0}", target.PaidUpTo);
      }

在Visual Studio 2010中,TestContext.Properties字典会自动填充。然而,Visual Studio 2012、2013和2015对单元测试进行了一些简化,现在必须手动编码值。

11.5.2 测试私有成员

单元测试的一个卖点是它对类内部的测试(以确保它们正常工作)特别有效。这里的假设是,如果每一个组件都是独立工作的,它们在一起正常工作的可能性就非常大。而事实上,单元测试可用于测试多个类的合作运行。那么,单元测试框架测试私有方法的效果如何呢?

.NET Framework的一个功能是它可以反射任意一个加载到内存中的类型,执行任意一个成员(无论它是私有的还是公有的)。这种功能会带来一定的性能损失,由于反射调用包含了一层额外的重定向操作,因此如果不断调用,就会对系统的运行造成很大的影响。尽管如此,对于测试来说,反射可以调用一个类的内部构造,而不需要担心这些调用所造成的潜在性能下降。

使用反射来访问类的非公有成员还存在另一个更大的缺陷:这种代码会变得非常混乱。在Subscription类上,为测试做准备:返回到CurrentStatus属性,把它的访问权限从public变更为private。

返回到单元测试,修改其主体,如下所示:

VB

      <TestMethod(), _
       DeploymentItem("Subscriptions.dll")> _
      Public Sub Private CurrentStatusTest()
          ' Arrange
          Dim s = new Subscription()
          s.PaidUpTo = null
          ' Act
          Dim t = s.GetType()
          Dim result As Object
          Result =  t.InvokeMember("CurrentStatus", BindingFlags.GetProperty |
             BindingFlags.Instance |BindingFlags.Public | BindingFlags.NonPublic, null, s,
      null)
          ' Assert
          Assert.IsInstanceOfType(result, GetType(Subscription.Status))
          Assert.AreEqual(Subscription.Status.Temporary, Cast(result, Subscription.Status))
      End Sub

C#

      [TestMethod()]
      [DeploymentItem("Subscriptions.dll")]
      public void Private CurrentStatusTest()
      {
          // Arrange
          Subscription s = new Subscription();
          s.PaidUpTo = null;
          // Act
          Type t = s.GetType();
          object result =  t.InvokeMember("CurrentStatus", BindingFlags.GetProperty |
             BindingFlags.Instance |BindingFlags.Public | BindingFlags.NonPublic, null, s,
      null);
          // Assert
          Assert.IsInstanceOfType(result, typeof(Subscription.Status));
          Assert.AreEqual(Subscription.Status.Temporary, (Subscription.Status)result);
      }

可以看到,上面的例子以InvokeMember方法的形式使用了反射。特别地,它检索类型(这将是Subscription类),然后调用InvokeMember来检索(GetProperty绑定标志)CurrentStatus属性值。接下来,判断结果是否为Subscription.Status类型并等于Temporary。