- Add project status and completion indicator - Add comprehensive project structure documentation - Enhance installation instructions with virtual environment setup - Add development tools section with VS Code debugging configuration - Document --all flag for full project testing - Add references to flowchart and implementation summary documentation - Update FAQ with new questions about debugging and virtual environment - Reflect current implementation features (call chain tracking, class module support, etc.) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
14 KiB
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 项目对象模型访问:
- 打开 Excel
- 文件 > 选项 > 信任中心
- 信任中心设置 > 宏设置
- 勾选"信任对 VBA 工程对象模型的访问"
- 重启 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)
系统通过以下步骤实现精确报错:
- 解析 VBA 代码: 识别所有 Sub/Function 过程
- 注入行号标签: 在可执行代码前插入数字标签(10, 20, 30...)
- 注入调用栈管理: 添加
LogEntry和LogExit调用 - 注入错误处理: 添加
On Error GoTo语句和错误处理块 - 创建 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) 确保注入的代码不会污染原始文件。
限制和注意事项
-
信任访问 VBA 项目: 需要在 Excel 信任中心启用"信任对 VBA 工程对象模型的访问"
- 路径: 文件 > 选项 > 信任中心 > 信任中心设置 > 宏设置 > 勾选"信任对 VBA 工程对象模型的访问"
-
行号标签冲突: 如果原始代码中已使用相同数值的行号标签,可能会产生冲突
-
复杂过程: 对于非常复杂的过程(包含大量 GoTo 语句),可能需要额外处理
-
模块类型支持: 支持标准模块(Type 1)和类模块(Type 2),不支持窗体模块(Type 3)
-
过程过滤: 全项目测试模式会自动排除以下过程:
- 以
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
系统会:
- 扫描所有标准模块和类模块
- 识别所有 Sub/Function 过程
- 过滤掉事件过程和内部方法
- 为所有模块注入调用栈管理
- 逐个执行测试并生成报告
自定义 Logger 模块
修改 LoggerInjector.LOGGER_MODULE_CODE 和 CALLSTACK_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"]
}
]
}
使用方法:
- 在 VS Code 中打开项目
- 按
F5或点击调试面板 - 选择 "Python: VBA Test Runner" 配置
- 可以在
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!