工作表(或 Word 文档)只不过是封装状态/数据的对象。
您可以特意使用您的代码将依赖的接口(例如IWorksheet
,或IDocument
)包装工作表/文档,但这将是一项巨大的努力,实际上几乎没有任何好处 - 单元测试将有使用该接口的“假”实现,该实现将负责存储测试数据/状态,以便您的测试可以断言您正在测试的代码按预期工作。完全矫枉过正。
相反,编写您的代码,以便给它一个Worksheet
实例(即避免与ActiveWorkbook
和/或工作ActiveSheet
),并做它需要做的任何事情。划分职责,这样当你调用一个方法时,你就没有 20,000 件事情要断言,以确保你的代码完成它所写的事情——但这不应该是任何新的或不同于你已经在做的事情,对吧?
'@Description("Adds a table named [tableName] on [sheet]. Returns the created table.")
Public Function AddTable(ByVal sheet As Worksheet, ByVal tableName As String) As ListObject
'TODO: implement
End Function
对这种方法的测试可能如下所示:
'@TestMethod
Public Sub AddsListObjectToSpecifiedWorksheet()
'Arrange
Dim sheet As Worksheet
Set sheet = ThisWorkbook.Worksheets.Add
Dim sut As MyAwesomeClass
Set sut = New MyAwesomeClass
Const tableName As String = "TestTable1"
If sheet.ListObjects.Count <> 0 Then _
Assert.Inconclusive "Sheet already has a table."
'Act
sut.AddTable sheet, tableName
'Assert
Assert.IsTrue sheet.ListObjects.Count = 1, "Table was not added."
sheet.Delete
End Sub
sheet
设置和清理代码可以移动到测试模块中的专用/TestInitialize
方法TestCleanup
,因为该测试模块中的每个测试方法都可能需要一个新的工作表来使用,因为您希望每个测试都是独立的而不是与其他测试共享任何状态。
将设置和清理代码提取到测试模块中的专用方法可以消除实际测试方法中的绒毛。毕竟,测试模块是一个标准模块,可以有自己的私有字段和模块级常量:
'@TestMethod
Public Sub ReturnsListObjectReference()
'Arrange
Dim sut As MyAwesomeClass
Set sut = New MyAwesomeClass
If testSheet.ListObjects.Count <> 0 Then _
Assert.Inconclusive "Sheet already has a table."
'Act
Dim result As ListObject
Set result = sut.AddTable(testSheet, tableName)
'Assert
Assert.IsNotNothing result, "Table was not returned."
Assert.AreSame result, testSheets.ListObjects(1), "Wrong table was returned."
End Sub
因此,您继续编写测试,每个测试都验证一个特定的行为:
'@TestMethod
Public Sub TableNameIsAsSpecified()
'Arrange
Dim sut As MyAwesomeClass
Set sut = New MyAwesomeClass
If testSheet.ListObjects.Count <> 0 Then _
Assert.Inconclusive "Sheet already has a table."
'Act
Dim result As ListObject
Set result = sut.AddTable(testSheet, tableName)
'Assert
Assert.AreEqual tableName, result.Name, "Table name wasn't set."
End Sub
这样,当您、未来的您或继承您的代码的任何人查看您的测试套件时,他们将确切地知道您的代码应该做什么,并且通过运行测试,他们会知道您的代码执行了预期的操作.
您是否希望在修改代码以使表格具有蓝色边框而不是绿色边框时中断测试,这完全取决于您和您的要求。
在您涉及 INI 文件的特定情况下,IMO 的“文件”部分是一个实现细节,您不希望单元测试依赖于网络上某处的某个文件。相反,您将拥有一个类或数据结构来保存配置键/值对;测试的“安排”部分将负责设置配置数据,当你“行动”时,你将配置传递给你的 SUT,然后断言结果状态与指定的配置匹配。
读/写实际 INI 文件的代码完全是另一个问题,它有自己的测试代码,这也可以避免碰到文件系统:你想测试你的代码,而不是Scripting Runtime是否完成它的FileSystemObject
工作。
请注意,就测试而言,是否AddTable
是实用程序标准程序模块的成员MyAwesomeClass
或某个实用程序标准程序模块绝对没有区别;单元测试不会告诉您如何重新组合/抽象功能和组织代码。
最新版本的 Rubberduck(预发布 2.1.x 版本)包括一个“假冒/存根”框架的开始,可以设置为通过挂钩到 VBA 运行时本身来拦截许多特定的标准库调用。例如,您不希望单元测试弹出 a MsgBox
,但如果您正在测试的方法需要一个,您可以MsgBox
在测试运行时拦截调用(甚至设置其返回值,例如模拟用户点击[Yes] 或 [No] 或 [Cancel]),但这完全是另一个话题。