【IT168 专稿】Microsoft Visual Studio Team System集成了多种功能,这些功能用于创建高质量代码。其中一项功能用于实现单元编码测试。执行单元测试能够实现测试驱动开发,另一项有用的功能是测试私有方法。在本文中,首先从测试驱动开发技术开始讲解,因为这是敏捷开发人员所提倡的重点。
下一篇:VS Team Edition在其它单元中的测试
·测试驱动开发
[TestClass]
public class UnitTests
{
[TestMethod]
public void TestMethod1()
{
// TODO: 添加测试逻辑
}
}
public class UnitTests
{
[TestMethod]
public void AddTest ()
{
int x = 1; int y = 2;
int expected = 3;
int actual = MyUtils.Calc.Add(x,y);
Assert.AreEqual<int>(expected, actual);
}
}
Assert类有一个方法,用于指定某个条件为“是”或“否”。在这种情况下,需要指定与实际结果相等的预期结果。尝试编译该测试会失败,因为还没有编写Calc类。下面必须编写充足的代码才能通过测试。换言之,需要在同一个解决方案中创建一个类库项目,实现Calc类,并从测试项目中引用该类库项目。创建Calc类之后就可以使用从单元测试生成方法说明的功能。即将鼠标在调用Add方法上悬停片刻,就会出现“Generate Method Stub For Add-in MyUtils.Calc”,如图1所示:
图1 从测试生成方法说明
·运行测试
编写满足编译器的代码之后,需要运行测试。首先选择单元测试文件,然后单击工具栏的专用按钮运行测试。工具栏还提供了其他选项,例如在调试器中运行测试,或者查看测试结果。执行之后将显示测试失败,如图2所示,因为本例中Visual Studio生成Add方法的默认实现是抛出异常。现在将重新编写代码以通过测试。
图2 测试失败
{
public static int Add(int x, int y)
{
return x+y;
}
}
最后,测试通过,如图3所示,接下来编写下一个测试。
图3 测试成功
·测试现有代码
如果向Calc类中添加一个Subtract方法,然后再右键单击该方法,那么将出现实现对应测试的选项,如图4所示。生成的测试代码基本上与上述AddTest方法相同。
图4 创建单元测试
·测试列表和测试运行配置
还有一种方法是从测试列表运行测试,该方法比之前说明的从专用模式更为常用。在测试项目创建之后自动生成一个扩展名为.vsmdi的文件。这个文件用于保存测试列表。在列表中包含许多关于测试运行的控制选项。
图5 测试管理器
打开.vsmdi文件将出现如图5显示的测试管理器窗口。管理器创建包含两个测试的UnitTest测试列表。现在可以从测试管理器工具栏运行列表所列的测试。
创建测试项目时还生成了另一个文件,该文件的扩展名为.testrunconfig,图6显示文件打开之后出现的窗口
图6 测试运行配置
文件包含影响测试运行方式的配置信息。例如,本例中可以为需要测试的代码定义代码覆盖率。运行测试结果和代码覆盖率信息,如图7所示。
图7. 代码覆盖率
在图5显示的测试列表执行中,Subtract方法被完全覆盖,但是Add方法却没有。通过Visual Studio Test菜单的Select Active Test Run Configuration选项可以创建并使用多个不同的.testrunconfig文件。
图8 测试目录
如图8所示,每个测试运行结果都存储在TestResults目录的子目录内对应的扩展名为.trx的文件中。.trx文件名和子目录名默认为计算机名、用户名、测试运行的时间戳,不过它们都可以更改。如图9所示,打开显示.trx文件的测试结果窗口,从中可以看到测试运行结果,还能对测试结果进行管理。
图9 测试结果
建立正确的测试环境
每个测试运行子目录都包含一个Out目录,测试和被测试代码的运行结果都位于其中。它们可能是重要文件,例如执行测试代码相关的部署。例如,在图6中可以找到Deployment选项卡,该选项卡提供了这项功能。另外,更具粒度的方法是在测试方法上使用DeploymentItem属性,该属性需要一个部署项。
public class MoreUnitTests
{
[DeploymentItem("people.xml")]
[TestMethod]
public string GetPersonIDFromFileTest()
{
string name="Bob";
int expected id=1;
// GetPersonIDFromFile方法从指定位置读取people.xml
int actual =
MyUtils.DataAccessLayer.GetPersonIDFromFile(name);
Assert.AreEqual<int>(expected, actual);
}
}
另外,还有一些用于帮助建立基于测试或测试组环境的功能。当然,这需要创建和安装数据库,因为数据库是测试方法的交互对象。在图6中可以注意到“Setup And Cleanup Scripts”选项卡,该选项卡提供在一组测试运行之前或之后运行脚本的功能。例如,这些脚本可以建立和清除数据库。另一个实现方法是定义带有特殊标注属性的方法。一对标注了AssemblyInitialize/AssemblyCleanup属性的方法会在测试程序集运行之前/后执行。一对标注了ClassInitialize/ClassCleanup属性的方法会在测试类运行之前/后执行。一对标注了TestInitialize/TestCleanup属性的方法会在测试类的每个测试方法运行之前/后执行。如下代码说明如何在一组测试运行之前建立数据库,之后再将其清除。
public class YetMoreUnitTests
{
[ClassInitialize()]
public static void Setup(TestContext testContext)
{
// 创建并安装数据库。
}
[ClassCleanup()]
public static void Teardown()
{
// 清除数据库。
}
[TestMethod]
public string GetPersonIDFromDatabaseTest()
{
string name="Bob";
int expected id=1;
// GetPersonIDFromDatabase方法从数据库读取数据。
int actual =
MyUtils.DataAccessLayer.GetPersonIDFromDatabase(name);
Assert.AreEqual<int>(expected, actual);
}
}
·测试私有方法
Visual Studio测试框架还有一个非常有用的功能,就是能够仅通过很小的工作量就能测试私有方法。如图10所示,首先选择一个私有方法,然后从菜单中选择创建单元测试选项。生成的单元测试使用私有访问修饰符,并且使用反射调用私有方法。如下代码示例显示了这个单元测试(带有一些装饰性的改变),不过已经省略了访问修饰符,而采用简单的MyUtils_CalcAccessor.SubtractHelper。
图10 创建私有方法的测试
public class UnitTests
{
[TestMethod]
public void SubtractHelperTest()
{
int x = 0;
int y = 0;
int expected = 0;
int actual;
actual = MyUtils_CalcAccessor.SubtractHelper(x, y);
Assert.AreEqual(expected, actual);
}
}