演练:带有“使用时生成”功能的测试先行支持
本主题演示如何使用使用时生成功能,该功能支持测试先行的开发。
“测试先行的开发”是一种软件设计方法。使用这种方法时,首先要基于产品规范编写单元测试,然后再编写使这些测试成功所需的源代码。Visual Studio 会在您首次在测试用例中引用新类型和成员时在源代码中生成这些类型和成员,之后再对其进行定义,从而支持测试先行的开发。
Visual Studio 在生成新类型和成员时会将对您的工作流的中断降到最低。您可以为类型、方法、属性、字段或构造函数创建存根,而无须离开代码中的当前位置。当您打开用于为类型生成指定选项的对话框时,焦点将在对话框关闭时立即返回到当前打开的文件。
“使用时生成”功能可用于与 Visual Studio 集成的测试框架。在本主题中,将演示 Microsoft 单元测试框架。
说明 |
---|
以下说明中的某些 Visual Studio 用户界面元素在你计算机上的名称或显示位置可能有所不同。这些元素取决于你所使用的 Visual Studio 版本和你所使用的设置。有关详细信息,请参阅 在 Visual Studio 中自定义开发设置。 |
创建一个 Windows 类库项目和一个测试项目
在 Visual C# 或 Visual Basic 中,创建一个新的 Windows 类库项目。根据您使用的语言而定,将该类库命名为 GFUDemo_VB 或 GFUDemo_CS。
在**“解决方案资源管理器”中,右击顶部的解决方案图标,指向“添加”,然后单击“新建项目”。在“新建项目”对话框左侧的“项目类型”窗格中,单击“测试”**。
在模板 窗格中,单击 单元测试项目 ,并接受 UnitTestProject1 的默认名称。下图显示了 Visual C# 中项目在该对话框中的外观。Visual Basic 中的该对话框与之类似。
“新建项目”对话框
单击**“确定”,关闭“新建项目”**对话框。现在,已做好开始编写测试的准备工作
从单元测试中生成新类
测试项目包含一个名为 UnitTest1 的文件。在**“解决方案资源管理器”**中双击此文件,以便在代码编辑器中将其打开。此时即已生成一个测试类和测试方法。
找到类 UnitTest1 的声明,并将其重命名为 AutomobileTest。在 C# 中,如果存在构造函数 UnitTest1(),请将其重命名为 AutomobileTest()。
说明 IntelliSense 现在为 IntelliSense 语句完成功能提供两种备选方式:“完成模式”和“建议模式”。对于在定义类和成员之前便要使用它们的情况,使用建议模式。当 IntelliSense 窗口打开时,可以按 Ctrl+Alt+空格键在完成模式和建议模式之间切换。有关更多信息,请参见使用 IntelliSense。当您在下一步中键入 Automobile 时,建议模式将有所帮助。
找到 TestMethod1() 方法并将其重命名为 DefaultAutomobileIsInitializedCorrectly()。在此方法内,创建名为 Automobile 的类的新实例,如以下各图所示。将出现一条指示编译时错误的波浪下划线,并且类型名称下面将出现一个智能标记。根据您是在使用 Visual Basic 还是 Visual C# 而定,该智能标记的准确位置有所不同。
Visual Basic
Visual C#
将鼠标指针放在智能标记上,可以看到一条错误消息,指示尚未定义名为 Automobile 的类型。单击智能标记或按 Ctrl+.(Ctrl+句点)打开“使用时生成”快捷菜单,如以下各图所示。
Visual Basic
Visual C#
现在,您有两个选择。您可以单击**“生成‘类 Automobile’”在测试项目中创建一个新文件,并用一个名为 Automobile 的空类填充该文件。通过这种方法,可以快速在当前项目的新文件中创建一个具有默认访问修饰符的新类。您也可以单击“生成新类型”,打开“生成新类型”**对话框。此对话框提供了一些选项,其中包括将类置于现有文件中,以及将文件添加到另一个项目。
单击**“生成新类型”,打开“生成新类型”对话框,如下图所示。在“项目”列表中,单击“GFUDemo_VB”或“GFUDemo_CS”**,以指示 Visual Studio 将文件添加到源代码项目(而不是测试项目)。
“生成新类型”对话框
单击**“确定”**关闭对话框并创建新文件。
在**“解决方案资源管理器”**中,在 GFUDemo_VB 或 GFUDemo_CS 项目节点的下方查看以验证是否存在新的 Automobile.vb 或 Automobile.cs 文件。在代码编辑器中,焦点仍然位于 AutomobileTest.DefaultAutomobileIsInitializedCorrectly 中。这意味着,您可以在中断时间最短的情况下继续自己的测试编写工作。
生成属性存根
假定产品规范规定 Automobile 类具有名为 Model 和 TopSpeed 的两个公共属性。默认构造函数必须用默认值 "Not specified" 和 -1 对这些属性进行初始化。以下单元测试将验证默认构造函数是否将属性设置为其正确的默认值。
将以下代码行添加到 DefaultAutomobileIsInitializedCorrectly。
Assert.IsTrue((myAuto.Model = "Not specified") And (myAuto.TopSpeed = -1))
Assert.IsTrue((myAuto.Model == "Not specified") && (myAuto.TopSpeed == -1));
由于代码引用了 Automobile 的两个未定义的属性,因此会出现智能标记。单击 Model 的智能标记,然后单击**“生成属性存根”**。同样为 TopSpeed 属性生成属性存根。
在 Automobile 类中,将从上下文中正确地推断出新属性的类型。
下图显示了智能标记快捷菜单。
Visual Basic
Visual C#
查找源代码
使用**“定位到”**功能定位到 Automobile.cs 或 Automobile.vb 源代码文件,以便可以验证是否生成了新属性。
利用**“定位到”**功能,您可以快速输入文本字符串(例如类型名称或名称的一部分),并通过单击结果列表中的元素转到所需的位置。
通过在代码编辑器中单击并按 Ctrl+,(Ctrl+逗号),打开**“定位到”对话框。在文本框中,键入 automobile。在列表中单击“Automobile”类,然后单击“确定”**。
下图中显示了**“定位到”**窗口。
“定位到”窗口
为新构造函数生成存根
在此测试方法中,您将生成一个构造函数存根,该存根将 Model 和 TopSpeed 属性初始化为具有指定的值。稍后,您将添加更多代码来完成测试。将以下附加测试方法添加到 AutomobileTest 类。
<TestMethod()> Public Sub AutomobileWithModelNameCanStart() Dim model As String = "Contoso" Dim topSpeed As Integer = 199 Dim myAuto As New Automobile(model, topSpeed) End Sub
[TestMethod] public void AutomobileWithModelNameCanStart() { string model = "Contoso"; int topSpeed = 199; Automobile myAuto = new Automobile(model, topSpeed); }
单击新类构造函数下方的智能标记,然后单击**“生成构造函数存根”**。请注意,在 Automobile 类文件中,新构造函数已检查了构造函数调用中使用的局部变量的名称,找到了具有 Automobile 类中的相同名称的属性,并在构造函数体中提供了代码来存储 Model 和 TopSpeed 属性中的参数值。(在 Visual Basic 中,新构造函数中的 _model 和 _topSpeed 字段是 Model 和 TopSpeed 属性的隐式定义的支持字段。)
当您生成新构造函数之后,DefaultAutomobileIsInitializedCorrectly 中的默认构造函数调用下方将出现一条波浪下划线。错误消息指出 Automobile 类没有不含参数的构造函数。若要生成没有参数的显式默认构造函数,请单击智能标记,然后单击**“生成构造函数存根”**。
为方法生成存根
假定规范规定:如果新 Automobile 的 Model 和 TopSpeed 属性设置为非默认值,则可以将其置于“正在运行”状态。将以下各行添加到 AutomobileWithModelNameCanStart 方法。
myAuto.Start() Assert.IsTrue(myAuto.IsRunning = True)
myAuto.Start(); Assert.IsTrue(myAuto.IsRunning == true);
单击 myAuto.Start 方法调用的智能标记,然后单击**“生成方法存根(Stub)”**。
单击 IsRunning 属性的智能标记,然后单击**“生成属性存根”**。Automobile 类现在包含以下代码。
Public Class Automobile Sub New(ByVal model As String, ByVal topSpeed As Integer) _model = model _topSpeed = topSpeed End Sub Sub New() ' TODO: Complete member initialization End Sub Property Model() As String Property TopSpeed As Integer Property IsRunning As Boolean Sub Start() Throw New NotImplementedException End Sub End Class
public class Automobile { public string Model { get; set; } public int TopSpeed { get; set; } public Automobile(string model, int topSpeed) { this.Model = model; this.TopSpeed = topSpeed; } public Automobile() { // TODO: Complete member initialization } public void Start() { throw new NotImplementedException(); } public bool IsRunning { get; set; } }
运行测试
在单元测试 菜单,指向 运行单元测试,然后单击 的所有测试。此命令将运行针对当前解决方案编写的所有测试框架中的所有测试。
本例中有两项测试,并且这两项测试按照预期都会失败。DefaultAutomobileIsInitializedCorrectly 测试失败的原因是 Assert.IsTrue 条件返回 False。AutomobileWithModelNameCanStart 测试失败的原因是 Automobile 类中的 Start 方法引发了异常。
下图中显示了**“测试结果”**窗口。
“测试结果”窗口
在**“测试结果”**窗口中,双击各测试结果行以转至各测试失败的位置。
实现源代码
将以下代码添加到默认构造函数,以便将 Model、TopSpeed 和 IsRunning 属性均初始化为其正确的默认值:"Not specified"、-1 和 True (true)。
Sub New() Model = "Not specified" TopSpeed = -1 IsRunning = True End Sub
public Automobile() { this.Model = "Not specified"; this.TopSpeed = -1; this.IsRunning = true; }
在调用 Start 方法时,只有在 Model 或 TopSpeed 属性设置为其非默认值的情况下,该方法才应将 IsRunning 标志设置为 true。从方法体中移除 NotImplementedException,并添加以下代码。
Sub Start() If Model <> "Not specified" Or TopSpeed <> -1 Then IsRunning = True Else IsRunning = False End If End Sub
public void Start() { if (this.Model != "Not specified" || this.TopSpeed != -1) this.IsRunning = true; else this.IsRunning = false; }
再次运行测试
在**“测试”菜单上,指向“运行”,然后单击“解决方案中的所有测试”。这次通过了测试。下图中显示了“测试结果”**窗口。
“测试结果”窗口