Misaka d9e11ff7dd chore: reorganize documentation structure
- Move vba_test_runner_flowchart.md to docs/ directory
- Remove IMPLEMENTATION_SUMMARY.md (content consolidated in README.md)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-04 23:38:22 +08:00

VBA 自动化测试与精确报错系统

通过代码编织Code Weaving技术实现 VBA 宏的精确到行的代码报错定位,并支持完整的调用链追踪。

项目状态

已完成 - 所有核心功能已实现并测试通过

核心特性

  • 精确行号定位: 捕获 VBA 错误的具体行号,不再只是"发生意外"
  • 源代码映射: 通过 Source Map 机制显示出错行的原始代码
  • 调用链追踪: 完整记录过程调用栈,追踪错误传播路径
  • 全项目测试: 自动发现并测试所有可测试的过程
  • 模块级支持: 支持标准模块和类模块的测试
  • 非侵入式: 测试过程不修改原始 Excel 文件
  • 详细报告: 提供清晰的测试结果输出,包含错误位置、源代码和调用链

项目结构

xlwings/
├── vba_test_runner.py              # 主脚本 - 核心测试系统实现
├── demo.xlsm                       # 演示 Excel 文件(包含测试用例)
├── README.md                       # 项目文档(本文件)
├── vba_test_runner_flowchart.md    # 详细流程图文档Mermaid 图表)
├── IMPLEMENTATION_SUMMARY.md       # 实现总结与技术细节
├── CLAUDE.md                       # Claude Code 开发指南
├── .gitignore                      # Git 忽略规则
├── .vscode/
│   └── launch.json                 # VS Code 调试配置
└── .venv/                          # Python 虚拟环境(需自行创建)

安装依赖

1. 创建虚拟环境(推荐)

# Windows
python -m venv .venv
.venv\Scripts\activate

# Linux/Mac
python3 -m venv .venv
source .venv/bin/activate

2. 安装依赖包

pip install xlwings pywin32

3. 配置 Excel 信任设置

在运行测试前,需要启用 VBA 项目对象模型访问:

  1. 打开 Excel
  2. 文件 > 选项 > 信任中心
  3. 信任中心设置 > 宏设置
  4. 勾选"信任对 VBA 工程对象模型的访问"
  5. 重启 Excel

快速开始

1. 使用演示文件测试

项目包含 demo.xlsm 演示文件,包含以下测试用例:

首先创建一个包含测试代码的 Excel 文件:

python create_demo.py

这将创建 demo.xlsm 文件,包含以下测试过程:

  • TestErrorProcedure: 除以零错误
  • TestTypeMismatch: 类型不匹配错误
  • TestSubscriptError: 下标越界错误
  • TestSuccessfulProcedure: 成功执行的测试

2. 运行测试

测试单个过程:

python vba_test_runner.py demo.xlsm Module1 TestSuccessfulProcedure

批量测试多个过程:

python vba_test_runner.py demo.xlsm Module1 TestErrorProcedure TestTypeMismatch TestSubscriptError TestSuccessfulProcedure

全项目自动化测试(自动发现并测试所有过程):

python vba_test_runner.py demo.xlsm --all

输出示例

单个测试

成功时:

[PASS] TestSuccessfulProcedure

失败时(包含调用链):

[FAIL] TestErrorProcedure
  Error: Division by zero
  Location: Module1.TestErrorProcedure:30
  Call Chain: Module1.MainProc -> Module1.TestErrorProcedure
  Source: result = x / y

批量测试

===== VBA 批量测试结果 =====

[1/4] [FAIL] TestErrorProcedure
  Error: Division by zero
  Location: Module1.TestErrorProcedure:30
  Source: result = x / y
[2/4] [FAIL] TestTypeMismatch
  Error: Type mismatch
  Location: Module1.TestTypeMismatch:20
  Source: y = x
[3/4] [FAIL] TestSubscriptError
  Error: Subscript out of range
  Location: Module1.TestSubscriptError:40
  Source: value = arr(5)
[4/4] [PASS] TestSuccessfulProcedure

===== Test Summary =====
Passed: 1/4
Failed: 3/4

全项目测试(--all 模式)

===== VBA 全项目自动化测试 =====

发现 8 个可测试的入口点
总过程数: 12

[1/8] 测试 Module1.TestErrorProcedure
  [FAIL] TestErrorProcedure
  Error: Division by zero
  Location: Module1.TestErrorProcedure:30
  Call Chain: Module1.TestErrorProcedure
  Source: result = x / y

[2/8] 测试 Module1.TestSuccessfulProcedure
  [PASS] TestSuccessfulProcedure
...

===== Test Summary =====
Passed: 5/8
Failed: 3/8

工作原理

代码编织Code Weaving

系统通过以下步骤实现精确报错:

  1. 解析 VBA 代码: 识别所有 Sub/Function 过程
  2. 注入行号标签: 在可执行代码前插入数字标签10, 20, 30...
  3. 注入调用栈管理: 添加 LogEntryLogExit 调用
  4. 注入错误处理: 添加 On Error GoTo 语句和错误处理块
  5. 创建 Source Map: 维护行号到源代码的映射

VBA 行号机制

VBA 的 Erl 函数会返回最近执行的行号标签:

10 x = 10
20 y = 0
30 result = x / y  ' Erl 将返回 30

调用链追踪

系统使用 CallStack 类模块追踪过程调用:

Sub LogEntry(procName, moduleName)
    CallStack.Push procName, moduleName
End Sub

Sub LogExit()
    CallStack.Pop
End Sub

Function GetCallChain() As String
    GetCallChain = CallStack.GetCallChain()  ' 返回 "Module1.Main -> Module1.Helper"
End Function

测试流程

原始 VBA 代码
    ↓
代码编织器注入行号、调用栈管理和错误处理
    ↓
注入 TestLogger 模块和 CallStack 类模块
    ↓
热替换目标模块代码
    ↓
执行 VBA 宏
    ↓
从 TestLogger 读取结果(包含调用链)
    ↓
格式化输出

架构设计

核心类

vba_test_runner.py
├── TestResult      (测试结果数据类)
│   ├── procedure_name          - 过程名称
│   ├── success                 - 测试是否成功
│   ├── error_number            - 错误代码
│   ├── error_description       - 错误描述
│   ├── error_line              - 错误行号
│   ├── source_code             - 源代码
│   ├── error_module            - 错误发生的模块 (新增)
│   └── call_chain              - 完整调用链 (新增)
│
├── CodeWeaver      (代码编织器类)
│   ├── parse_modules()                 - 解析所有模块
│   ├── parse_procedures()              - 解析 VBA 过程
│   ├── weave_procedure()               - 编织单个过程
│   ├── weave_procedure_with_callstack() - 编织过程(带调用栈)
│   ├── weave_module_all_procedures()   - 编织模块的所有过程
│   ├── weave_all_modules()             - 编织所有模块
│   ├── _inject_line_numbers()          - 注入行号标签
│   ├── _inject_error_handler()         - 注入错误处理
│   └── _inject_error_handler_with_callstack() - 注入错误处理(带调用栈)
│
├── LoggerInjector  (日志模块注入器类)
│   ├── inject_or_replace()     - 注入或替换 Logger 模块
│   ├── LOGGER_MODULE_CODE      - TestLogger 模块的 VBA 代码
│   └── CALLSTACK_CLASS_CODE    - CallStack 类模块的 VBA 代码
│
└── TestRunner      (执行控制器类)
    ├── run_test()              - 执行单个测试
    ├── run_all_tests()         - 执行全项目测试 (新增)
    ├── discover_all_tests()    - 发现所有可测试过程 (新增)
    ├── _get_vba_code()         - 读取 VBA 代码
    ├── _replace_module_code()  - 热替换模块代码
    ├── _execute_macro()        - 执行宏
    ├── _get_test_result()      - 获取测试结果
    ├── _is_entry_point()       - 判断是否为测试入口点 (新增)
    ├── _weave_all_modules_inplace() - 就地编织所有模块 (新增)
    └── _run_single_test()      - 执行单个测试(批量用) (新增)

TestResult 结构

@dataclass
class TestResult:
    procedure_name: str      # 过程名称
    success: bool            # 测试是否成功
    error_number: int = 0    # 错误代码
    error_description: str = ""  # 错误描述
    error_line: int = 0      # 错误行号
    source_code: str = ""    # 源代码
    error_module: str = ""   # 错误发生的模块
    call_chain: str = ""     # 完整调用链 (如: "Module1.A -> Module1.B")

关键技术点

行号标签规则

  • 使用纯数字标签: 10, 20, 30... (不带冒号)
  • 只在可执行代码前注入
  • 跳过声明区Dim, Private 等)
  • 跳过注释行和空行
  • 跳过现有的 On Error 语句

错误处理模板(带调用栈)

Call TestLogger.LogEntry("{proc_name}", "{module_name}")
On Error GoTo Auto_Err_Handler_{proc_name}

    ... 原有代码 ...

    Call TestLogger.LogExit()
    Call TestLogger.LogSuccess()
    Exit Sub/Function

Auto_Err_Handler_{proc_name}:
    Call TestLogger.LogError("{proc_name}", "{module_name}", Err.Number, Err.Description, Erl)
    Call TestLogger.LogExit()

CallStack 类模块

' CallStack 类维护调用栈
Private m_Stack As Collection

Public Sub Push(procName, moduleName)
    m_Stack.Add moduleName & "." & procName
End Sub

Public Sub Pop()
    m_Stack.Remove m_Stack.Count
End Sub

Public Function GetCallChain() As String
    ' 返回 "Module1.A -> Module1.B -> Module2.C"
    GetCallChain = Join(parts, " -> ")
End Function

热替换不保存

使用 wb.Close(SaveChanges=False) 确保注入的代码不会污染原始文件。

限制和注意事项

  1. 信任访问 VBA 项目: 需要在 Excel 信任中心启用"信任对 VBA 工程对象模型的访问"

    • 路径: 文件 > 选项 > 信任中心 > 信任中心设置 > 宏设置 > 勾选"信任对 VBA 工程对象模型的访问"
  2. 行号标签冲突: 如果原始代码中已使用相同数值的行号标签,可能会产生冲突

  3. 复杂过程: 对于非常复杂的过程(包含大量 GoTo 语句),可能需要额外处理

  4. 模块类型支持: 支持标准模块Type 1和类模块Type 2不支持窗体模块Type 3

  5. 过程过滤: 全项目测试模式会自动排除以下过程:

    • Worksheet_Workbook_Document_ 开头的事件过程
    • Logger 相关过程LogEntry、LogExit、LogError 等)

扩展开发

添加新的测试过程

在 Excel 文件的 VBA 模块中添加你的测试过程:

Sub YourTestProcedure()
    ' 你的测试代码
End Sub

然后运行:

python vba_test_runner.py your_file.xlsm Module1 YourTestProcedure

使用全项目测试

全项目测试模式会自动发现并测试所有过程:

python vba_test_runner.py your_file.xlsm --all

系统会:

  1. 扫描所有标准模块和类模块
  2. 识别所有 Sub/Function 过程
  3. 过滤掉事件过程和内部方法
  4. 为所有模块注入调用栈管理
  5. 逐个执行测试并生成报告

自定义 Logger 模块

修改 LoggerInjector.LOGGER_MODULE_CODECALLSTACK_CLASS_CODE 可以自定义日志记录逻辑。

程序化使用

from vba_test_runner import TestRunner

# 单个测试
runner = TestRunner("demo.xlsm", visible=False)
result = runner.run_test("Module1", "TestErrorProcedure")

# 全项目测试
runner = TestRunner("demo.xlsm", visible=False)
results = runner.run_all_tests()

# 发现所有测试
tests = runner.discover_all_tests()
for test in tests:
    print(f"{test['module']}.{test['procedure']}")

开发工具

VS Code 调试配置

项目包含 .vscode/launch.json 调试配置,可直接在 VS Code 中调试:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python: VBA Test Runner",
            "type": "debugpy",
            "request": "launch",
            "program": "${workspaceFolder}/vba_test_runner.py",
            "console": "integratedTerminal",
            "args": ["demo.xlsm", "--all"]
        }
    ]
}

使用方法:

  1. 在 VS Code 中打开项目
  2. F5 或点击调试面板
  3. 选择 "Python: VBA Test Runner" 配置
  4. 可以在 launch.json 中修改 args 来测试不同的场景

查看流程图

详细的技术流程图请查看 vba_test_runner_flowchart.md,包含:

  • 系统架构概览
  • 单个测试执行流程
  • 全项目测试流程
  • 代码编织流程
  • 错误处理与调用链追踪
  • 类关系图

实现总结

查看 IMPLEMENTATION_SUMMARY.md 了解:

  • 项目完成状态
  • 已测试的验证场景
  • 关键技术实现细节
  • 已解决的问题

常见问题

Q: 为什么测试后原始文件没有被修改?

A: 系统使用热替换技术,在内存中修改代码,测试完成后使用 Close(SaveChanges=False) 不保存更改。

Q: 如何测试类模块中的方法?

A: 系统支持类模块Type 2的测试。在全项目测试模式下类模块中的公共方法会被自动发现和测试。

Q: 调用链是如何追踪的?

A: 系统在每个过程入口注入 LogEntry 调用,在出口注入 LogExit 调用。CallStack 类模块维护一个栈结构,记录所有正在执行的过程,错误发生时可以生成完整的调用链。

Q: 全项目测试模式和手动指定过程有什么区别?

A: 全项目测试模式(--all)会:

  • 自动发现所有可测试的过程
  • 一次性编织所有模块的代码
  • 逐个执行测试并生成汇总报告
  • 更适合大规模测试和回归测试

手动指定过程模式更适合:

  • 调试单个过程
  • 快速验证修复
  • 选择性测试某些功能

Q: 如何在 VS Code 中调试?

A: 项目包含 .vscode/launch.json 配置文件。在 VS Code 中按 F5 即可启动调试,可以在配置中修改测试参数。

Q: 可以捕获运行时警告吗?

A: 当前版本只捕获错误。要捕获警告,需要修改 Logger 模块来处理 InfoMessage 事件。

Q: 虚拟环境是必须的吗?

A: 强烈推荐使用虚拟环境来隔离项目依赖。项目包含 .gitignore 规则来忽略虚拟环境目录。

许可证

MIT License

贡献

欢迎提交 Issue 和 Pull Request

Description
VBA Automated Testing and Precise Error Reporting System
Readme 83 KiB
Languages
Python 100%