如何设计和单元测试VBA类模块以将格式化表添加到文档

时间:2017-08-07 02:38:17

标签: vba excel-vba word-vba rubberduck excel

这个问题的动机是获得一个具体的例子,说明修改文档时可用的单元可测试代码。作为背景,我理解这些类非常适合定义和验证事物,例如:

类模块是否适合修改文档似乎是“依赖”(请参阅​​Mat的Mug答案:In VBA should code that modifies document be avoided in a class module)而且我找不到很多将单元测试用于修改代码的示例一个文件(也许这有充分的理由?)

无论如何,基于我对事物的有限理解,我认为,对于“将格式化表格添加到文档”的单词加载项,类模块提供了合理方法的基础将格式化的表格添加到文档中...... (如果我错了,请告诉我)

虽然我已经标记了VBA-Excel,但我真的对MS Word示例(这是如此缺乏)更感兴趣,所以通过一个相当简单的MS Word示例,让我说我有代码将格式化的表添加到文档中指定范围。

出于示例的目的,我们假设:

  • 事件的基本顺序是:
    • 将默认表添加到文档
    • 随后根据INI文件格式化
  • 为所有表指定的格式为:
    • 表边框线颜色
    • table row1着色颜色
  • INI文件指定了几个表
    • TBL1界= wdRed
    • TBL2界= wdGreen
    • TBL1-底纹= wdRed
    • TBL1-底纹= wdGreen

所以我的下一个问题是:

  • 我应该计划多少课程?
    • 1用于添加和格式化表格
    • 1用于读取INI文件数据
  • 每个类模块的结构是什么样的?
  • 我(我可以)单位测试代码:
    • 修改文档(添加表格)?
    • 读取INI文件?

我不希望任何人提供实际的工作代码;但是非常感谢伪代码,一般建议以及一些特定的指针。

注意:如果这个问题太宽泛,我会很高兴分成单独的问题

1 个答案:

答案 0 :(得分:1)

工作表(或Word文档)只不过是一个封装状态/数据的对象。

您可以不用自己的方式将工作表/文档包含在您的代码所依赖的界面中(例如IWorksheetIDocument),但这将是一项巨大的努力,实际上几乎没有任何好处 - 单元测试必须使用"假的"该接口的实现,负责存储测试数据/状态,以便您的测试可以断言您正在测试的代码按预期工作。完全矫枉过正。

相反,编写代码以便为其提供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设置&amp;清理代码可以移动到测试模块中的专用TestInitialize / TestCleanup方法,因为该测试模块中的每个测试方法都可能需要一个新的工作表才能使用,因为您需要这样做。我希望每个测试都是独立的,不要与其他测试共享任何状态。

提取设置&amp;清理代码到测试模块中的专用方法可以从实际测试方法中去除绒毛。毕竟,测试模块是一个标准模块,可以拥有自己的私有字段和模块级常量:

'@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

这样,当您,未来的您或继承您的代码的任何人查看您的测试套件时,他们将确切知道您的代码应该做什么,并通过运行测试他们& #39; ll 知道您的代码符合其预期的目标。

您是否希望在修改代码时断开测试以使表格具有蓝色边框而不是绿色,这完全取决于您和您的要求。

在涉及INI文件的特定情况下,IMO&#34;文件&#34; part是一个实现细节,你不希望单元测试依赖于网络上的某个文件。相反,您将拥有一个类或数据结构来保存配置键/值对;测试&#34;安排&#34; part将负责设置配置数据,当你&#34; act&#34;将配置传递给SUT,然后断言结果状态与指定的配置匹配。

读取/写入实际INI文件的代码完全是另一个问题,它有自己的测试代码,这也可以避免命中文件系统:你想测试你的代码,而不是脚本运行时&#39; FileSystemObject完成了它的工作。

请注意,AddTableMyAwesomeClass的成员还是某些公用事业标准程序模块,就测试而言绝对没有区别;单元测试不会告诉您如何重组/抽象功能并组织代码。

最新版本的Rubberduck(预发布2.1.x版本)包括假冒/存根&#34;的开头。通过挂钩到VBA运行时本身,可以设置拦截许多特定标准库调用的框架。例如,您不希望单元测试弹出MsgBox,但如果您要测试的方法需要一个,则可以在测试运行时拦截MsgBox来电(甚至设置其返回值,例如模拟用户点击[是]或[否]或[取消]),但这完全是另一个主题。