Skip to content

使用Assert编写单元测试

本教程展示如何编写一个小函数、使用Assert包为其添加测试,并从IDE内部运行这些测试。

Assert包

Assert包提供三个模块——ExactStrictPermissive——它们共享相同的十五成员API:

模块字符串比较数值数据类型必须匹配
Exact区分大小写是——55.0 不相等
Strict区分大小写
Permissive不区分大小写

这三个模块在发布构建中会被编译掉:每个成员都标记了 [DebugOnly(True)],因此断言调用在生产EXE中零运行时开销。测试与生产代码位于同一项目中,并在IDE中由完整调试器运行。

最常用的成员:

  • Exact.AreEqual expected, actual —— 如果两个值不同则失败
  • Exact.IsTrue condition —— 如果条件为 False 则失败
  • Exact.IsFalse condition —— 如果条件为 True 则失败
  • Exact.Fail message —— 无条件记录失败
  • Exact.Succeed —— 显式记录通过(在条件路径末尾很有用)

每个失败的断言记录源位置、期望值和实际值以及可选的消息字符串。结果显示在调试控制台面板中。

添加包

打开项目 → 引用(Ctrl+T)→ 可用包,勾选Assert。点击确定。三个模块(ExactStrictPermissive)现在在作用域中,无需任何 Imports 语句。

被测试的函数

向项目添加一个标准模块(在项目资源管理器中右键点击项目,然后添加 → 模块)。命名为 StringUtils。添加以下函数:

vb
' Pads s on the left with padChar until it reaches totalWidth characters.
' If s is already at or beyond totalWidth, it is returned unchanged.
Public Function PadLeft(ByVal s As String, _
                        ByVal totalWidth As Long, _
                        Optional ByVal padChar As String = " ") As String
    If Len(s) >= totalWidth Then
        PadLeft = s
    Else
        PadLeft = String(totalWidth - Len(s), Left$(padChar, 1)) & s
    End If
End Function

PadLeft 是一个很好的测试对象:它有明确的规格说明、带有默认值的可选参数,以及多个不同的边界情况。

编写测试

添加第二个模块 TestStringUtils。每个测试是一个 Public Sub,测试函数的一个方面。保持每个Sub简短——理想情况下每个Sub一个逻辑场景,名称描述其检查内容。

vb
Public Sub TestPadLeft_Normal()
    ' Three spaces prefix "hi" to reach width 5
    Exact.AreEqual "   hi", PadLeft("hi", 5)
End Sub

Public Sub TestPadLeft_CustomPadChar()
    ' Zero-pad to width 5
    Exact.AreEqual "00042", PadLeft("42", 5, "0")
End Sub

Public Sub TestPadLeft_AtWidth()
    ' Already at width -- no change
    Exact.AreEqual "hello", PadLeft("hello", 5)
End Sub

Public Sub TestPadLeft_ExceedsWidth()
    ' Already longer than width -- not truncated
    Exact.AreEqual "toolong", PadLeft("toolong", 5)
End Sub

Public Sub TestPadLeft_EmptyString()
    ' Empty input -- result is all padding
    Exact.AreEqual "   ", PadLeft("", 3)
End Sub

Public Sub TestPadLeft_SingleChar()
    ' Width of 1, input already 1 char -- no change
    Exact.AreEqual "x", PadLeft("x", 1)
End Sub

这些测试覆盖了:正常情况、自定义填充字符、边界情况、超出边界情况、空输入和最小输入。

运行测试

有两种方式运行测试Sub:

  1. CodeLens —— 将光标放在测试Sub内的任何位置。Sub 行上方的CodeLens条显示一个 ▶ Run 按钮。点击它运行该Sub。结果立即显示在调试控制台中。

  2. 从Sub内部按F5 —— 将光标放在Sub内并按F5。twinBASIC运行该过程,并在其返回或断言失败时停止。

要批量运行所有测试,添加一个运行器Sub按顺序调用每个测试:

vb
Public Sub RunAllTests()
    TestPadLeft_Normal
    TestPadLeft_CustomPadChar
    TestPadLeft_AtWidth
    TestPadLeft_ExceedsWidth
    TestPadLeft_EmptyString
    TestPadLeft_SingleChar
    Debug.Print "All PadLeft tests passed."
End Sub

将光标放在 RunAllTests 内并按F5(或点击CodeLens条中的**▶ Run**)。如果任何断言失败,执行在失败行停止,调试控制台显示哪个断言失败、其期望值和实际值以及源位置。

测试错误路径

有时函数应该对错误输入引发错误。使用 On Error Resume NextErr.Number 进行测试:

vb
Public Sub TestPadLeft_ZeroWidth()
    ' A width of 0 is technically valid -- the string is returned unchanged
    ' if it is already zero-length, and unchanged otherwise.
    Exact.AreEqual "hi", PadLeft("hi", 0)
    Exact.AreEqual "", PadLeft("", 0)
End Sub

如果你期望函数引发错误:

vb
Public Sub TestSomethingThatShouldRaise()
    On Error Resume Next
    SomeFunctionThatRaises 0    ' call that should fail
    If Err.Number = 0 Then
        Exact.Fail "expected an error, but none was raised"
    End If
    On Error GoTo 0
End Sub

选择正确的模块

默认使用Exact——其最严格的比较语义防止测试因错误原因而通过。当被测试代码有意不区分大小写,或比较的值无论数值类型如何都应相等时,切换到StrictPermissive

vb
' Exact would fail because "hello" ≠ "Hello" (case differs)
Strict.AreEqual "HELLO", LCase$("HELLO")  ' fails -- "hello" ≠ "HELLO"
Permissive.AreEqual "HELLO", LCase$("HELLO")  ' passes -- case-insensitive

三个模块的完整文档见:

测试组织

随着项目增长,将测试保持在它们所测试的代码附近。一种常见约定:

  • 每个关注点一个生产模块:StringUtilsDateUtilsFileHelpers、……
  • 每个生产模块一个测试模块:TestStringUtilsTestDateUtilsTestFileHelpers、……
  • TestRunner 模块中有一个顶层 RunAll Sub,调用每个模块的运行器

由于所有测试Sub在发布构建中会被编译掉([DebugOnly(True)]),这种组织不会给发布可执行文件增加任何开销。

下一步

  • Assert包参考 —— 所有十五个成员的详细说明:Assert包
  • 窗体基础 —— 构建窗体以可视化地承载小型测试工具:窗体基础
  • Windows API —— 编写和测试封装Declare的函数:调用Windows API

twinBASIC及其LOGO版权为作者"韦恩"所有