第四章 - 单元测试
运行单元测试可以检验代码的正确性,帮助您在应用程序发布之前发现可能出现的错误。
您可以在 SnapDevelop IDE 中使用 xUnit 测试工具来创建、管理和运行您的单元测试。
xUnit 单元测试工具
SnapDevelop 默认安装的是 xUnit 单元测试框架。安装完成后,您可以在 SnapDevelop 中创建 xUnit 测试 项目用于运行单元测试。xUnit 测试 项目在创建后将自动包含 xUnit 相关的软件包。
SnapDevelop 还提供了 xUnit 测试 窗口,用于运行单元测试、查看测试结果以及调试单元测试。从 测试 > xUnit 测试 菜单可以打开 xUnit 测试 窗口。
xUnit 测试 窗口将自动显示解决方案中的测试方法。如果您的解决方案中没有包含 xUnit 测试 项目,xUnit 测试 节点将为空。请在解决方案中添加一个 xUnit 测试 项目,或者使用我们的测试项目(从 此处 下载)。
运行单元测试
当您打开 xUnit 测试 窗口时,当前解决方案的所有测试方法以树形图的结构显示:xUnit 测试 根节点 > 项目 > 命名空间 > 测试方法(如方法中包含了多组测试数据,则还会在下一层显示数据)。当您新增或删除项目时,树形图会自动更新。您可以点击窗口导航条的 展开 或 折叠 图标(或右键点击树形图然后选择 展开 或 折叠 )来查看树形图。或者利用搜索框来快速找到测试方法。
您可以通过以下方式运行测试方法:
点击导航条中的 在视图中运行所有测试 图标运行树形图中可见的所有测试方法。如果您通过搜索框过滤掉了部分测试方法,那么只有树形图中可见的测试方法会运行。
选中树形图中的节点,然后点击导航条中的 运行 图标(或者右键菜单中选择 运行)来运行当前节点下的测试方法。
如果您已经运行过测试方法,您可以点击导航条中的 重复上次运行 图标再次运行测试。
如果您已经运行过测试方法,您可以点击导航条中的 运行失败的测试 图标运行上次未能通过的测试,或者点开 运行 图标的下拉菜单,然后选择 运行未运行的测试 或 运行已通过的测试。
查看测试结果
运行测试方法以后,您可以在 xUnit 测试 窗口的树形图中查看测试方法的耗时、特性以及错误消息。如果方法中使用了 Trait 特性,将在 特性 列中显示Trait 特性中定义的方法特性。
当您点击树形图中的节点时, xUnit 测试 窗口的底部区域将会显示 组摘要 或 测试详细信息摘要。
当您选择 测试方法 级别以上的节点时(例如项目节点、命名空间节点),组摘要 将显示当前组的统计信息,包括测试方法总数、总时长、以及测试结果。
当您选择 测试方法 节点时,测试详细信息摘要 将显示当前的测试方法的详细信息,包括方法名、测试结果、方法所在的代码文件及代码行、以及耗时(如果已运行的话)。您可以点击代码文件打开文件查看当前的测试方法。或者在树形图中右键点击测试方法,然后选择 转到测试。如果测试失败的话,您还将在这里看到错误堆栈信息。
调试单元测试
如果测试方法未按预期运行,您可以调试测试方法。
您可以点开 xUnit 测试 窗口导航条中的 运行 图标的下拉菜单,然后选择 调试、在视图中调试所有测试、或 调试上次运行。或者右键点击树形图中的节点,然后从右键菜单中选择 调试。
xUnit 使用教程
将 xUnit 添加到现有解决方案
打开现有项目
为了更好配合文档说明,我们提供了一个用于测试的类库项目,请从 此处 下载。
下载后,请在 SnapDevelop 中打开该项目,然后打开 Calculator.cs 文件,在该文件中添加实际的业务逻辑。这是修改后的 Calculator.cs 文件:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Calculator
{
public class Calculator
{
public static double Addition(double num1, double num2)
{
return num1 + num2;
}
public static double Subtraction(double num1, double num2)
{
return num1 - num2;
}
public static double Multiplication(double num1, double num2)
{
return num1 * num2;
}
public static double Division(double num1, double num2)
{
return num1 / num2;
}
}
}
如上所示,这是一个非常简单的类,执行非常简单的加减乘除操作,并返还每个操作的结果。接下来我们用这个简单的类来展示如何使用 xUnit 测试项目。
请注意 static 关键字的使用,使用它可避免后续做实例化。
新增 xUnit 测试项目
现在,我们在解决方案中创建一个新的 xUnit 测试项目。右键点击解决方案,然后点击 添加 > 新建项目。
选择 xUnit 测试,点击 下一步。
将项目命名为 Calculator.Tests,然后点击 下一步。在下个窗口中,点击 创建。
这将创建一个 xUnit 测试项目,其中只包含了一个类作为测试的起始点。
由于我们会创建自己的类来做测试,因此可以删除此对象。右键点击 UnitTest1.cs 对象,然后选择 删除。
使用 [Fact] 标签
添加测试类
右键点击 Calculator.Tests 项目并选择 添加 > 类,在 添加新项 窗口中选择 xUnit 测试类,将其命名为 CalculatorTests.cs 并点击 创建。
CalculatorTests 类默认包含以下代码:
using Xunit;
namespace Calculator.Tests
{
public class CalculatorTests
{
[Fact]
public void Test1()
{
}
}
}
请删除掉下面代码:
[Fact]
public void Test1()
{
现在需要对 Calculator.Tests 项目增加一个对 Calculator 项目的引用。请右键点击 Calculator.Tests 项目下的 依赖项 的节点,然后选择 添加项目引用。
在 引用管理 窗口中选中项目 Calculator 的复选框并点击 确定。
Calculator.Tests 项目下面这将新增一个 Calculator 项目的引用节点。意味着您现在可以在测试项目中引用 Calculator 项目的类方法。
接下来,您可以在刚才创建的 CalculatorTests 测试类中编写测试代码了。请跟随下一节的步骤进行操作。
编写测试方法
您将在 CalculatorTests 类上编写测试代码用于测试 Calculator 类中的 Addition 方法。
有一个非常有用的技巧是为您的测试方法应用命名约定(naming convention)。命名约定的其中一条规定您需要在测试方法的名称中使用单词 Should 以明确该方法“应该”执行某些测试任务。但是在本节中,您将使用自己的命名约定,以测试方法的名称开头,然后是它“应该”做什么的简单说明。所以建议将其命名为:
public void Addition_ShouldCalculateSimpleValues()
{
}
请注意它返回 void,那是因为测试方法通常不需要返回任何值。
测试方法的代码行中通常包含三个部分:实例化(Instantiation)、执行(Action)和判断(Assertion)。
在实例化部分您将声明测试需要使用到的变量。在执行部分您将定义测试的方法以及其他需要执行的操作。在判断部分您将定义判断测试通过的条件,在这里,您将用到 Assert 对象。
Assert 对象有许多 API,可以帮助您确认测试通过或失败。您可以使用 SnapDevelop IDE 的智能代码完成功能查看 Assert 的所有 API。
将以下代码添加到 CalculatorTests 类的测试方法中:
[Fact]
public void Addition_ShouldCalculateSimpleValues()
{
// Instantiation
double expected = 9;
// Action
double actual = Calculator.Addition(3, 6);
// Assertion
Assert.Equal(expected, actual);
}
请注意这里用到了 [Fact] 。在下一小节您将使用 [Theory] 。[Fact] 是不带方法参数的测试方法,用于测试确定的唯一结果。[Theory] 是带方法参数的测试方法,用于测试可变化的结果。
您将先使用 [Fact] 做测试(因为非常明显 3 加 6 就是等于 9)。稍后将使用 [Theory] 做测试。
现在,点击 测试 > xUnit 测试 菜单选项以打开 xUnit 测试 窗口。您会注意到 xUnit 测试已经出现在测试窗口内了。
运行测试
点击 xUnit 测试 窗口中的 在视图中运行所有测试 图标。等待项目编译并运行测试,然后您将看到测试结果。
该测试定义如果将 3 和 6 传递给 Addition
方法,它应该返回 9。
使用 [Theory] 标签
上一节中完成的测试内容比较简单,它展示了 [Fact] 标签的最基本的使用方法。现在,我们将利用 [Theory] 标签来做一些更接近真实使用场景的测试。我们会用几组不同的设定值来测试代码,尽量从多个角度来验证代码是否有漏洞。接下来,我们将 [Fact] 改成 [Theory]。
编写测试方法
[Theory] 通常用于测试不同的场景或数据的运行结果;需要传递不同的值到代码中来模拟不同场景。因此,您将需要使用到 [InlineData] 标签,该标签将设置不同的场景来测试数据。现在,添加一些场景到方法中去:
[Theory]
[InlineData(3, 6, 9)]
[InlineData(4, 3, 7)]
[InlineData(-10, 10, 0)]
[InlineData(34, 5.33, 39.33)]
[InlineData(-10, -10, -20)]
public void Addition_ShouldCalculateSimpleValues(double num1, double num2, double expected)
{
// Action
double actual = Calculator.Addition(num1, num2);
// Assertion
Assert.Equal(expected, actual);
}
请注意这里为方法添加了三个参数,以便从 [Theory] 接收值。此外,通过调用您的实际方法会使得参数更加贴近现实。
保存修改,您会看到(如下图)刚刚添加的每个 [InlineData] 标签都已成为 xUnit 测试 窗口中的测试项。
运行测试
您现在可以再次点击 在视图中运行所有测试 来运行所有的测试。所有测试应该都能成功通过。
修复失败的测试
目前为止,所有测试都是成功通过的。但假如某个测试未能通过的话,该如何处理呢?
来看一下接下来的测试实例:
[Theory]
[InlineData(8, 4, 2)]
[InlineData(-9, 3, -3)]
[InlineData(15, 0, 0)]
public void Division_ShouldCalculateSimpleValues(double num1, double num2, double expected)
{
// Action
double actual = Calculator.Division(num1, num2);
// Assertion
Assert.Equal(expected, actual);
}
该测试实例测试了 Division
方法的三种使用场景。
如果您运行这些测试,会注意到其中一个失败了。
Division
方法中一个 double
类型的数值在除以零时并没有返回零,它实际上返回的是 undetermined 或 infinite。
这意味着我们需要修改一下代码,以便它可以通过这个测试场景。这也是做测试的意义,让我们的代码能够兼容所有应该要兼容的要求和场景。
因此,我们现在对 Calculator 项目的 Calculator.cs 文件的 Division
方法更改如下:
public static double Division(double num1, double num2)
{
if (num2 == 0)
{
// Refactored logic for Division by zero
return 0;
}
else
{
return num1 / num2;
}
}
同时,由于现在知道了一个确定的结果([Fact]),即所有 double
类型的数值除以零都应该返回零,那么可以将该测试修改成为 [Fact]。因此,对 CalculatorTests.cs 文件修改如下:
[Theory]
[InlineData(8, 4, 2)]
[InlineData(-9, 3, -3)]
public void Division_ShouldCalculateSimpleValues(double num1, double num2, double expected)
{
// Action
double actual = Calculator.Division(num1, num2);
// Assertion
Assert.Equal(expected, actual);
}
[Fact]
public void Division_ShouldDivideByZero()
{
// Instantiation
double expected = 0;
// Action
double actual = Calculator.Division(15, 0);
// Assertion
Assert.Equal(expected, actual);
}
保存后,xUnit 测试 窗口中的测试方法列表将更新。
再次运行所有测试,所有的测试都应该成功通过。
调试测试
在上面的小节中我们编写了几个测试实例,有时我们可能会需要调试一个测试以确保它是按照既定的方式在正常工作。
首先在测试代码中添加断点。可以在代码编辑器中通过点击左侧的侧边栏来为该行添加断点,或者右键点击该代码行然后选择 断点 > 插入断点。
然后在 xUnit 测试 窗口中右键点击要调试的测试项,在弹出菜单中选择 调试。
注意:本步骤是调试测试,不是调试应用。因此,请不要直接启动和调试本测试项目案例中引用的项目 Calculator。如要调试测试,请确保如本步骤描述:在 xUnit 测试 窗口中右键点击要调试的测试项,然后选择 调试;或者在代码编辑器中右键点击然后选择 调试测试。
然后您可以开始调试代码(就像调试其他应用程序的代码一样)。
注意事项
请记住:
- 您应该完善代码以通过各种场景的测试,而不是通过修改测试以便代码可以通过。
- 您应该为尽可能多的场景([Fact] 和 [Theory])创建测试实例,这可以帮助您开发出更高质量的工作代码部署到生产环境。
- 您的测试项目(本节中的 Calculator.Tests )在生产环境中不一定需要。
- xUnit 测试 项目可以帮助您在开发过程中写出更高质量的代码。