# 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. 创建虚拟环境(推荐) ```bash # Windows python -m venv .venv .venv\Scripts\activate # Linux/Mac python3 -m venv .venv source .venv/bin/activate ``` ### 2. 安装依赖包 ```bash pip install xlwings pywin32 ``` ### 3. 配置 Excel 信任设置 在运行测试前,需要启用 VBA 项目对象模型访问: 1. 打开 Excel 2. 文件 > 选项 > 信任中心 3. 信任中心设置 > 宏设置 4. 勾选"信任对 VBA 工程对象模型的访问" 5. 重启 Excel ## 快速开始 ### 1. 使用演示文件测试 项目包含 `demo.xlsm` 演示文件,包含以下测试用例: 首先创建一个包含测试代码的 Excel 文件: ```bash python create_demo.py ``` 这将创建 `demo.xlsm` 文件,包含以下测试过程: - `TestErrorProcedure`: 除以零错误 - `TestTypeMismatch`: 类型不匹配错误 - `TestSubscriptError`: 下标越界错误 - `TestSuccessfulProcedure`: 成功执行的测试 ### 2. 运行测试 测试单个过程: ```bash python vba_test_runner.py demo.xlsm Module1 TestSuccessfulProcedure ``` 批量测试多个过程: ```bash python vba_test_runner.py demo.xlsm Module1 TestErrorProcedure TestTypeMismatch TestSubscriptError TestSuccessfulProcedure ``` **全项目自动化测试**(自动发现并测试所有过程): ```bash 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. **注入调用栈管理**: 添加 `LogEntry` 和 `LogExit` 调用 4. **注入错误处理**: 添加 `On Error GoTo` 语句和错误处理块 5. **创建 Source Map**: 维护行号到源代码的映射 ### VBA 行号机制 VBA 的 `Erl` 函数会返回最近执行的行号标签: ```vba 10 x = 10 20 y = 0 30 result = x / y ' Erl 将返回 30 ``` ### 调用链追踪 系统使用 `CallStack` 类模块追踪过程调用: ```vba 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 结构 ```python @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 语句 ### 错误处理模板(带调用栈) ```vba 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 类模块 ```vba ' 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 模块中添加你的测试过程: ```vba Sub YourTestProcedure() ' 你的测试代码 End Sub ``` 然后运行: ```bash python vba_test_runner.py your_file.xlsm Module1 YourTestProcedure ``` ### 使用全项目测试 全项目测试模式会自动发现并测试所有过程: ```bash python vba_test_runner.py your_file.xlsm --all ``` 系统会: 1. 扫描所有标准模块和类模块 2. 识别所有 Sub/Function 过程 3. 过滤掉事件过程和内部方法 4. 为所有模块注入调用栈管理 5. 逐个执行测试并生成报告 ### 自定义 Logger 模块 修改 `LoggerInjector.LOGGER_MODULE_CODE` 和 `CALLSTACK_CLASS_CODE` 可以自定义日志记录逻辑。 ### 程序化使用 ```python 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 中调试: ```json { "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](vba_test_runner_flowchart.md),包含: - 系统架构概览 - 单个测试执行流程 - 全项目测试流程 - 代码编织流程 - 错误处理与调用链追踪 - 类关系图 ### 实现总结 查看 [IMPLEMENTATION_SUMMARY.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!