技术开发 频道

VS Team Edition在其它单元中的测试

  【IT168 专稿】Microsoft Visual Studio Team System集成了多种功能,这些功能用于创建高质量代码。其中一项功能用于实现单元编码测试。并在Microsoft ASP.NET内部运行测试代码。在本文中,首先从测试驱动开发技术开始讲解,因为这是敏捷开发人员所提倡的重点。

上一篇:使用VS Team Edition进行单元测试
 
  ·预期的错误

  在某些测试中,开发人员可能需要提供一些错误条件。所以,可以标注测试方法为ExpectedException属性,并设定为预期的异常类型。在如下示例中,即使抛出DivideByZeroException异常,测试也能通过。

[TestClass]
public class UnitTests
{
     [TestMethod]
     [ExpectedException(
typeof(DivideByZeroException))]
    
public void DivideTest()
    {
        
int x = 1; int y = 0;
        MyUtils.Calc.Divide(x, y);
    }
}

  ·数据驱动测试

  为了彻底测试,并保证代码的完全覆盖率,尤其是测试所有边缘条件,有时候需要使用不同的输入参数和对同一段代码进行反复测试,以及针对不同的返回值的测试。除了编写每一种不同情况的测试代码之外,测试框架还能从数据库获取不同的测试参数。这种方法称为数据驱动测试。采用如下SQL脚本对建立名为TestData的Microsoft SQL Server Express Edition数据库,之后再将其清除。TestData数据库包含用于数据驱动测试的数据。

-- create.sql
USE master
GO
DROP DATABASE TestData
GO
CREATE DATABASE TestData
GO
use TestData
GO
CREATE TABLE CalcTestData
(
  x
int NOT NULL,
  y
int NOT NULL,
  expected
int NOT NULL,
  shouldpass
bit NOT NULL
)
GO

INSERT INTO CalcTestData (x, y, expected, shouldpass)
  
values (1, 1, 2, 1)
INSERT INTO CalcTestData (x, y, expected, shouldpass)
  
values (1, 2, 1, 0)
GO

-- destroy.sql
USE master
GO
DROP DATABASE TestData
GO

  同样使用如下命令脚本执行上述SQL脚本。这些命令脚本从测试运行配置的“Setup And Cleanup Scripts”选项卡引用,所以它们可以在测试运行之前/后执行。

rem create.cmd
sqlcmd
-S .\SQLExpress -i create.sql

rem destroy.cmd
sqlcmd
-S .\SQLExpress -i destroy.sql

 

  最后,单元测试使用如下代码示例中的数据。注意SQL脚本必须部署到测试运行目录,然后才能由命令脚本执行。

[TestClass]
public class UnitTests
{
  
private TestContext tc;
  
public TestContext TestContext
  {
    
get { return tc; }
    
set { tc = value; }
  }

  [TestMethod]
  [DataSource(
    
@"Provider=SQLOLEDB;Data Source=.\sqlexpress;
    Integrated Security=SSPI;Initial Catalog=TestData
",
    
"CalcTestData")]
  [DeploymentItem(
"create.sql")]
  [DeploymentItem(
"destroy.sql")]
  
public void DataDrivenAddTest()
  {
    
int x = (int)TestContext.DataRow[0];
    
int y = (int)TestContext.DataRow[1];
    
int expected = (int)TestContext.DataRow[2];
    
int actual = MyUtils.Calc.Add(x, y);

    
bool shouldpass = (bool)TestContext.DataRow[3];
    
if (shouldpass)
      Assert.AreEqual
<int>(expected, actual);
    
else
      Assert.AreNotEqual
<int>(expected, actual);
  }
}

  DataSource属性设定关于连接字符串的信息,字符串指定包含数据驱动测试数据的测试数据库和表。这些数据对于TestContext对象的DataRow属性可用。在测试集合执行时,TestContext包含所有测试框架维护的有用信息。因为测试类提供TestContext类型的公共属性TestContext,所以测试框架提供当前TestContext对象的引用。每次调用测试都会读取CalcTestData表中的一行,TestContext类的DataRow属性表示每次调用的当前行。图11显示了测试的输出结果。


11  数据驱动测试输出

  ·数据驱动测试的数据管理

  这里有两个值得一提的相关问题。首先,为了降低最后一个示例的大量移动部分,建议不要使用配置文件抽象数据源信息。实际上,如果有多个开发人员运行测试,他们可能都使用不同的数据源信息。虽然本文并不会说明如何实现,但是DataSource属性允许可以完成。在线MSDN文档有关于这方面的解释。第二,每次运行测试时,在示例中可以使用SQL脚本轻松地创建和安装包含测试数据的数据库。当然,这项技术比较适合用于已经存在的测试数据库,而对于其它环境也许并不能良好工作。管理数据库是一个问题,并且没有适合所有情况的方法。但是抽象数据源信息到配置文件,提供了适合不同方法的灵活性。

  ·测试Web服务代码

  开发人员不仅可以本地化运行单元测试代码,还能远程运行代码,例如Web服务。采用与本地代码相同的方法编写纯粹的Web服务测试驱动开发显然不太可能,因为测试Web服务时,实际上是本地测试Web服务代理。因为这并不是Web服务客户端的典型开发方法,所以开发人员不能真正编写调用代理的测试,然后再编写满足测试的代理。有经验的Web服务开发人员建议,首先生成Web服务描述语言(Web Services Description Language,WSDL)规则,然后再分别开发基于规则的Web服务和Web服务客户端实现。因为过于复杂,所以客户端代理代码和服务器端说明代码通常都工具自动生成。

  然而,可以首先生成WSDL规则,再生成基于此规则的客户端代理,然后在测试中调用代理。由于Web服务还不存在,执行测试会失败。所以必须实现并部署按照WSDL规则的Web服务。直到最终传递给测试,生成Web服务都可能出现问题。虽然并不是纯粹的测试驱动开发,但是非常接近。在本示例中,使用Visual Studio传递最简单的路径,然后向解决方案添加Web服务项目,并对其进行测试。右击任意Web服务方法,通过与本地方法相同的途径生成测试。图12显示如何操作。这对于生成Web服务代理和使用该代理的测试有一定影响。如下代码中显示了生成的带有少量装饰性修改的测试方法。
 


图12  测试Web服务

 

[TestClass()]
public class WebUnitTests
{
       ...
  [TestMethod()]
  
public void AddTest()
  {
    CalcWebService target
= new CalcWebService();

    
int x = 1;
    
int y = 2;
    
int expected = 3;
    
int actual;

    actual
= target.Add(x, y);
    Assert.AreEqual(expected, actual);
  }
}

  当运行测试时,与其它测试的运行并没有差别。在常规案例中,需要保证Web服务的宿主服务器在测试运行之前已经启动。但是本示例中,Visual Studio承载测试框架,并用于开发和承载Web服务(同本例),Visual Studio Web服务器(Cassini)按需求启动。另外,在常规案例中,如果重新部署Web服务,那么必须在新位置指定Web服务客户端配置信息文件。但是,这种情况下Visual Studio承载测试框架,并用于开发和承载Web服务,它自动实现这些操作。

  当Web服务由Cassini承载,并且尝试在Visual Studio意外运行时(例如Mstest.exe、命令行测试工具、或者Team Foundation Server的Team Build),该测试就会出现问题。无论是Mstest.exe,还是Team Foundation Server的Team Build都不能启动Cassini,即使能够启动,它们也没有向代理传送Cassini所选动态端口的方法。在这个示例中,需要获取测试框架才能实现所有重要功能。为了通知测试框架启动Cassini监听专用虚拟目录(映射专用物理目录)的请求,需要应用AspNetDevelopmentServer属性。另外,需要使用WebServiceHelper类的TryUrlRedirection方法通知端口代理Cassini选择的监听端口,测试框架信息存储在TestContext中的。注意PathToWebRoot变量用于避免Cassini使用的硬编码物理地址。这个变量在Visual Studio“工具”菜单的“选项”设置,然后为设置应用程序根目录,如图13所示。



图13  设置PathToWebRoot变量

 

[TestClass()]
public class WebUnitTests
{
  [TestMethod()]
  [AspNetDevelopmentServer(
"mysettings",
    
@"%PathToWebRoot%\MyWebSite", "/MyWebSite")]
  
public void AddTest()
  {
    CalcWebService target
= new CalcWebService();

    
int x = 1;
    
int y = 2;
    
int expected = 3;
    
int actual;

    WebServiceHelper.TryUrlRedirection(
      target, TestContext,
"mysettings");

    actual
= target.Add(x, y);
    Assert.AreEqual(expected, actual);
  }
}

  如果使用Mstest.exe在Visual Studio意外运行测试,那么它将启动Cassini,并正常访问Web服务,如图14所示。


图14  MSTest在ASP.NET内部运行测试


  测试Web服务时,实际上是在本地进程的上下文中测试本地Web服务代理,再与远程Web服务通信。开发人员并没有测试Web服务的方法,或者它调用运行在宿主Web服务上下文中的方法。如果希望对运行在ASP.NET上下文中的测试进行编码,应该怎样做呢?可以在ASP.NET内部承载测试框架,并运行测试。这是当然可能的。为什么需要这样做?部分测试需要进行一些ASP.NET上下文方面的检查,例如会话。

  考虑一个简单的示例,Web服务使用辅助方法添加两个数字,然后在ASP.NET会话中存储结果。
 

public class Helper
{
  
public static void AddPlacingResultInSession(int x, int y)
  {
    
int ret = x + y;
    HttpContext.Current.Session[
"lastsum"] = ret;
  }
}

[WebService(Namespace
= "http://example.org/calcservice")]
[WebServiceBinding(ConformsTo
= WsiProfiles.BasicProfile1_1)]
public class CalcWebService : System.Web.Services.WebService
{
  [WebMethod]
  
public int Add2(int x, int y)
  {
    Helper.AddPlacingResultInSession(x, y);
    
return (int)Session["lastsum"];
  }
}

 

 

  如果右击Helper类的AddPlacingResultInSession方法,可以创建与前面类似的单元测试。这一次,需要在会话中指定正确的结果。

[TestMethod()]
[HostType(
"ASP.NET")]
[AspNetDevelopmentServerHost(
"%PathToWebRoot%\\MyWebSite",
  
"/MyWebSite")]
[UrlToTest(
"http://localhost/MyWebSite")]
public void AddPlacingResultInSessionTest()
{
  
int x = 1; int y = 2;
  
int expected = 3;

  TestProject.HelperAccessor.AddPlacingResultInSession(x, y);
  Assert.AreEqual
<int>(expected,
    (
int)HttpContext.Current.Session["lastsum"]);
}

  只有执行了ASP.NET中的测试和被测试代码,测试才能成功(并且确实成功了!)。那么它如何工作的呢?当HostType("ASP.NET")和UrlToTest属性应用到前述测试方法时,起到了特殊作用。测试框架使用UrlToTest属性设定的URL指定目标Web应用程序。注意URL消息包含动态分配的端口号,本例中AspNetDevelopmentServerHost属性通知测试框架启动Cassini。之后,测试框架为目标Web应用程序更改web.config文件,并将其设置为针对VSEnterpriseHelper.axd端点路径的临时配置的HTTP模块和HTTP处理程序。安装的HTTP模块和处理程序使得测试框架称为目标Web应用程序的引导程序。通常,这种测试在ASP.NET内部运行。

  ·小结

  如果测试代码贯穿开发生命周期,那么能更快抓住和修改bug,并且完成高质量编码。Visual Studio Team Edition的两个版本(for Software Developers和for Software Testers)包含对单元测试代码的支持。读者可以使用它们的功能实践测试驱动开发或者测试现有代码,代码甚至还能运行在远程Web应用程序中。
 

0
相关文章