问题:我们集团正打算为接近完成的应用程序创建一个单元测试。我们要通过多个登录用户运行这些测试,而且还不知道如何从单独的测试类中做到这一点。另外一些几乎相同的测试也只是存在数据输入和输出的差异。我们可以重复利用这些测试中的代码吗?我们只创建了50个测试,不过启用测试的条件已经丢失。在这种情况下,我们集团怎样才能在创建大量测试之后继续往下走呢?我们已经在网上了解过单元测试的相关信息,但是其中大部分都是侧重于系统架构,而系统架构是我们所不能更改的。恐怕我们只能放弃单元测试了,但是管理规定要求有完整的代码覆盖。 怎样使用Visual Studio中的T4代码生成工具
专家解答答:不要放弃单元测试!单元测试的发展历史尚浅。正是因为这个原因,在本文给出的回答中给出的建议不会冗繁。这里我们所提供的是适用于所有项目的实用型单元测试。如果你想在测试中实现系统设计结构化,那你想对了,因为被简单测试过的设计和演变设计这两者关系很近。你不可以进行测试,除非你进行了测试驱动型开发并且具备一个可以承受失败的架构。
测试已有架构和系统要求指定一个匹配系统的测试方法。一些设计从单元测试的角度看来似乎是可测试的,不过还是要依赖手动测试。这些设计将业务逻辑放到用户界面中。假设你的逻辑已经存在于业务层,下一个测试问题则来自粒度和数据库访问。直接的数据库访问意味着测试性能被大大减缓,数据库的更改中断了测试,也破坏了实际代码,当然测试数据必须被保存。
我们假设你遇到了所有这些问题。保存测试数据最简单的方法就是在每次运行时都重新创建数据库,并将其作为测试的一部分。虽然这样做会减缓测试,但是如果每天都有测试那么不如将测试交给夜构建(Nightly Builds)。并不是说这种做法很完美,但是它可以让你摆脱测试的压力,让你专注于编写测试。我们必须在数据库发生更改时,与数据库同步保存所有测试。测试设置可以在长达几周的开发过程中被完全弃用,除非这些更改是实时发生的。
我们可能会认为测试比较特殊,然后选择不按平时对待代码的方法来对待它,不过测试仍然是代码,我们要向对待其他代码一样给予足够重视。对象设计,重复利用,有意义的命名和高质量的代码对于单元测试都很重要。 Visual Studio 2010中的灾难恢复功能
首先,要问的问题是:“对象可以做什么?”以及“为什么它是一个单独的对象?”由VS创建的测试文件提供了一种假象,即测试类的目的是为了测试位于测试下的单独类。快速检查测试声明中使用的属性会发现其真实意图是为一套测试,运行测试和解除测试条件等设定条件,属性标记方法,如ClassInitialize,TestIntialize,ClassCleanup,TestCleanUp和TestMethod。当然你不能从一个单独类测试多个用户——生成代码或许暗示可以这样做,但是这个类的设计意图并非如此。在这种情况下,最简单的方法是利用为每次登录所设计的不同类。
重复利用在测试中很重要。寻找重新构建代码以减少冗余的机会,适当的时候可以使用内置的帮助代码。例如,不同登录情况下可以使用相同的基类。
另一个在测试期间可以最大限度提高代码重复利用的技巧是数据驱动型测试。当你执行这种测试的时候,该技巧或许与你所使用的数据库并无关系。它可以让你通过外部机制声明输入和输出的数值,并巩固那些只通过输入和输出值来区分的测试。你可以使用任意ODBC资源,包括SQL Server。笔者更喜欢Excel,因为它支持测试装置添加更多测试条件。你需要访问TestCentext,编写测试以便使用测试数据并确保其执行:
[TestClass]
public class TestClass
{
public TestContext TestContext {get; set;}
[TestMethod]
[DataSource("System.Data.OleDb",
@"Provider=Microsoft.Jet.OLEDB.4.0;Data
Source=TestData.xls;Extended Properties='Excel
8.0;HDR=Yes;IMEX=1';",
"Sheet1$", DataAccessMethod.Sequential)]
[DeploymentItem("TestData.xls")]
public void TestMethod()
{
int x = Convert.ToInt32(TestContext.DataRow["x"]);
int y = Convert.ToInt32(TestContext.DataRow["y"]);
int result = Convert.ToInt32(TestContext.DataRow["Result"]);
Assert.AreEqual(result, Class1.AddValues(x,y), "Addition is not correct");
}
}
TestCentext允许对测试环境的访问,包括数据源。数据源指向名为TestData的Excel电子表格,该表格包含名为Sheet 1的工作表。符号$作为Excel命名的一部分被添加。该表格包含了栏目标头x,y和Results,这些都可以在调用Assert之前设置变量。
进行数据测试型测试的时候,数据部署是个令人头疼的问题。VS测试通常不能在本地运行,但是可以被复制到测试目录中。这样就可以插入代码以便测量代码覆盖并保障任意本地资源,如文本文件没有在测试期间被更改的时候。由于测试期间必须提供数据,所以必须将数据部署到临时目录中。DeploymentItem属性赋予了文件名称,它应该作为测试环境的一部分执行。文件名包含了相对路径,在这一例子中,路径相对于建成的可执行文件。为了将电子表格放在可知性文件目录中,可以将其包含在项目中,并将其设置为Output Directory目录以随时复制。如果你运行测试装备编辑电子表格以创建额外测试,一定要保护数据文件,这可以通过把它放置在源代码控制中实现。
组织大量测试并将测试与项目期望联系起来是具有挑战性的。一个新出现的重要技术遵守的是Rspec等工具所使用的命名模式。该方法记录了什么情境会通过明确规定的条件来测试。例如,这些类可能是:
[TestClass()]
public class When_an_admin_is_logged_in
...
[TestClass()]
public class When_a_normal_user_is_logged_in
...
[TestClass()]
public class When_any_user_is_logged_in
...
类的初始化会记录相应用户,然后运行一系列授权测试。这些测试反应了你的实际测试情况:
[TestMethod()]
public void User_class_CanCreate_property_is_true()
你可以想象得到完全基于反射的报告是怎样。根据你的授权规则,这一测试可能属于admin或any_user测试类。你或许想用有创意的命名方式来描述系统的设计方式。如果授权基于架构,例如该测试或许是any_user类中的property_matches_configuration。多数情况下,你可以通过从相同基类中获取同一类,从该类中受保护方法中运行真实代码以及执行终端类中的Asserts等方法来改善重复利用。
避免过长名称很容易,但是你永远不能调用这些方法——只能在测试输出内容和记录中读取方法。完整句法结构有助于创建标准,这样,不同人创建的测试名称会基本一致。
在文章开篇的问题中提到测试条件,并称管理对代码覆盖很感兴趣。代码覆盖是一种可怕的数据,与按照代码行数来衡量效率比起来它好不到哪里去。代码覆盖不能确定系统是否经过很好的测试,因为它不能考虑限制条件和其他可能导致失败的故障点。覆盖唯一指明的是哪些代码没有经过测试。首先要对代码进行的操作不是覆盖,而是确保它的使用。如果代码被使用了,要确定相应的假设情景并添加更多测试。