Files
xlwings/docs/vba_test_runner_flowchart.md
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

17 KiB

VBA 自动化测试系统 - 流程图文档

目录

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

系统架构概览

整体数据流

graph TB
    A[用户输入测试请求] --> B{测试模式}
    B -->|单个过程| C[run_test]
    B -->|全项目| D[run_all_tests]

    C --> E[TestRunner 初始化]
    D --> E

    E --> F[CodeWeaver 解析 VBA 模块]
    F --> G[注入行号标签和错误处理]
    G --> H[LoggerInjector 注入日志模块]
    H --> I[热替换模块代码]
    I --> J[执行 VBA 宏]
    J --> K[捕获执行结果]
    K --> L[返回 TestResult]

    L --> M{测试成功?}
    M -->|是| N[显示成功信息]
    M -->|否| O[显示详细错误信息]
    O --> P[包含错误位置、源代码、调用链]

    N --> Q[清理资源,关闭 Excel]
    P --> Q

    style A fill:#e1f5ff
    style N fill:#c8e6c9
    style O fill:#ffcdd2
    style Q fill:#fff9c4

单个测试执行流程

TestRunner.run_test() 详细流程

flowchart TD
    Start([开始: run_test]) --> OpenExcel[1. 打开 Excel 文件<br/>xw.App.visible]
    OpenExcel --> ReadCode[2. 读取原始 VBA 代码<br/>_get_vba_code]

    ReadCode --> WeaveCode[3. 编织模块代码<br/>weave_module_all_procedures]
    WeaveCode --> ParseProcs[3.1 解析所有过程<br/>parse_procedures]
    ParseProcs --> InjectLineNum[3.2 注入行号标签<br/>_inject_line_numbers]
    InjectLineNum --> InjectErrHandler[3.3 注入错误处理和调用栈<br/>_inject_error_handler_with_callstack]

    InjectErrHandler --> InjectLogger[4. 注入 Logger 模块<br/>inject_or_replace]
    InjectLogger --> ReplaceCode[5. 热替换模块代码<br/>_replace_module_code]
    ReplaceCode --> Execute[6. 执行宏<br/>_execute_macro]

    Execute --> GetResult[7. 获取测试结果<br/>_get_test_result]
    GetResult --> CheckResult{结果字符串}

    CheckResult -->|SUCCESS| CreateSuccess[创建成功 TestResult]
    CheckResult -->|ERROR| ParseError[解析错误信息字符串<br/>格式: ERROR + 模块.过程 + 行号 + 编号 + 描述 + 调用链]

    ParseError --> ExtractInfo[提取错误模块、过程、行号]
    ExtractInfo --> LookupSource[从 Source Map 查找源代码<br/>source_map[line]]
    LookupSource --> CreateError[创建失败 TestResult]

    CreateSuccess --> Cleanup[8. 清理资源<br/>_cleanup]
    CreateError --> Cleanup
    Cleanup --> End([结束])

   异常处理[Exception] --> CreateErrorResult[创建异常 TestResult]
    CreateErrorResult --> Cleanup

    style Start fill:#e1f5ff
    style End fill:#e1f5ff
    style CreateSuccess fill:#c8e6c9
    style CreateError fill:#ffcdd2
    style Cleanup fill:#fff9c4

全项目测试流程

TestRunner.run_all_tests() 批量测试流程

flowchart TD
    Start([开始: run_all_tests]) --> Discover[发现所有测试<br/>discover_all_tests]

    Discover --> ParseModules[解析 VBA 项目<br/>parse_modules]
    ParseModules --> IterateModules[遍历所有模块]
    IterateModules --> IterateProcs[遍历每个过程]
    IterateProcs --> CheckEntry{是否是入口点?<br/>_is_entry_point}

    CheckEntry -->|排除事件/Logger过程| Filter[过滤掉]
    CheckEntry -->|是| AddToList[添加到测试列表]

    AddToList --> Filter
    Filter --> MoreModules{更多模块?}
    MoreModules -->|是| IterateModules
    MoreModules -->|否| PrintStats[打印统计信息<br/>总过程数、可测试数]

    PrintStats --> OpenExcel[打开 Excel 文件]
    OpenExcel --> WeaveAll[一次性编织所有模块<br/>_weave_all_modules_inplace]

    WeaveAll --> InjectLoggerModule[注入 TestLogger 和 CallStack]
    InjectLoggerModule --> WeaveEach[编织每个模块]
    WeaveEach --> ReplaceAll[热替换所有模块代码]

    ReplaceAll --> RunTests[执行所有测试]
    RunTests --> TestLoop[遍历入口点测试]

    TestLoop --> InitLogger[初始化 TestLogger<br/>Initialize]
    InitLogger --> RunSingle[执行单个测试<br/>_run_single_test]

    RunSingle --> ExecuteMacro[执行宏]
    ExecuteMacro --> GetSingleResult[获取结果]
    GetSingleResult --> PrintResult[打印测试结果<br/>print_test_result]

    PrintResult --> MoreTests{更多测试?}
    MoreTests -->|是| TestLoop
    MoreTests -->|否| PrintSummary[打印测试汇总<br/>print_summary]

    PrintSummary --> Cleanup[清理资源]
    Cleanup --> End([结束])

    style Start fill:#e1f5ff
    style End fill:#e1f5ff
    style PrintStats fill:#fff9c4
    style PrintSummary fill:#fff9c4
    style Cleanup fill:#ffe0b2

代码编织流程

CodeWeaver 代码注入详细流程

flowchart TD
    Start([开始: weave_procedure_with_callstack]) --> Parse[解析过程<br/>parse_procedures]

    Parse --> FindProc{找到过程?}
    FindProc -->|否| Error[抛出 ValueError]
    FindProc -->|是| ExtractLines[提取过程代码行<br/>start_line 到 end_line]

    ExtractLines --> InjectLineNums[注入行号标签<br/>_inject_line_numbers]

    InjectLineNums --> InitCounter[初始化计数器 = 10]
    InitCounter --> LineLoop[遍历代码行]

    LineLoop --> CheckLine{检查行类型}
    CheckLine -->|声明行| SkipDec[跳过]
    CheckLine -->|注释行| SkipComment[跳过]
    CheckLine -->|空行| SkipEmpty[跳过]
    CheckLine -->|On Error| SkipOnError[跳过]
    CheckLine -->|过程定义/结束| SkipProc[跳过]
    CheckLine -->|可执行代码| AddLabel[添加行号标签<br/>10, 20, 30...]

    AddLabel --> SaveToMap[保存到 source_map<br/>行号 -> 源代码]
    SaveToMap --> Increment[计数器 += 10]

    SkipDec --> MoreLines{更多行?}
    SkipComment --> MoreLines
    SkipEmpty --> MoreLines
    SkipOnError --> MoreLines
    SkipProc --> MoreLines
    Increment --> MoreLines

    MoreLines -->|是| LineLoop
    MoreLines -->|否| InjectHandler[注入错误处理<br/>_inject_error_handler_with_callstack]

    InjectHandler --> DetermineType[确定过程类型<br/>Sub or Function]
    DetermineType --> RemoveExisting[删除现有 On Error]

    RemoveExisting --> InsertLogEntry[在第一个可执行语句后插入:<br/>TestLogger.LogEntry<br/>On Error GoTo Handler]

    InsertLogEntry --> InsertExitBlock[在 End 前插入退出块:<br/>TestLogger.LogExit<br/>TestLogger.LogSuccess<br/>Exit Sub/Function]

    InsertExitBlock --> InsertHandlerBlock[插入错误处理块:<br/>Auto_Err_Handler:<br/>TestLogger.LogError<br/>TestLogger.LogExit]

    InsertHandlerBlock --> ReplaceProc[替换原过程]
    ReplaceProc --> Return[返回编织后的代码<br/>和 source_map]
    Return --> End([结束])

    Error --> End

    style Start fill:#e1f5ff
    style End fill:#e1f5ff
    style AddLabel fill:#c8e6c9
    style InsertLogEntry fill:#b3e5fc
    style InsertHandlerBlock fill:#ffccbc

行号标签注入规则

graph TD
    A[原始 VBA 代码] --> B{行类型判断}

    B --> C[声明块<br/>Dim/Private/Public/Const]
    B --> D[注释行<br/>' 开头]
    B --> E[空行]
    B --> F[On Error 语句]
    B --> G[过程定义行<br/>Sub/Function]
    B --> H[过程结束行<br/>End Sub/Function]
    B --> I[退出语句<br/>Exit Sub/Function]
    B --> J[可执行代码]

    C --> Z[跳过,不注入]
    D --> Z
    E --> Z
    F --> Z
    G --> Z
    H --> Z
    I --> Z

    J --> K[注入行号标签<br/>10, 20, 30...]
    K --> L[记录到 source_map]

    style C fill:#ffcdd2
    style D fill:#ffcdd2
    style E fill:#ffcdd2
    style F fill:#ffcdd2
    style G fill:#ffcdd2
    style H fill:#ffcdd2
    style I fill:#ffcdd2
    style J fill:#c8e6c9
    style Z fill:#ffe0b2

错误处理与调用链追踪

TestLogger 和 CallStack 工作流程

sequenceDiagram
    participant Test as 测试流程
    participant Logger as TestLogger
    participant Stack as CallStack
    participant VBA as VBA 过程

    Test->>Logger: Initialize()
    Logger->>Logger: m_TestStatus = "SUCCESS"

    Test->>VBA: 调用过程 A

    VBA->>Logger: LogEntry("A", "Module1")
    Logger->>Stack: Push("A", "Module1")
    Stack->>Stack: m_Stack.Add("Module1.A")

    VBA->>VBA: 执行代码 (带行号标签)

    alt 过程 A 调用过程 B
        VBA->>Logger: LogEntry("B", "Module1")
        Logger->>Stack: Push("B", "Module1")
        Stack->>Stack: m_Stack.Add("Module1.B")

        VBA->>VBA: 执行代码
        Note over VBA: 20: x = 1 / 0  ❌ 除零错误

        VBA->>Logger: LogError("B", "Module1", 11, "Division by zero", 20)
        Logger->>Logger: m_TestStatus = "ERROR"
        Logger->>Stack: GetCallChain()
        Stack-->>Logger: "Module1.A -> Module1.B"
        Logger->>Logger: 保存错误信息

        VBA->>Logger: LogExit()
        Logger->>Stack: Pop()
    end

    VBA->>Logger: LogExit()
    Logger->>Stack: Pop()

    Test->>Logger: GetResult()
    Logger-->>Test: "ERROR + Module1.B + 20 + 11 + Division by zero + Module1.A -> Module1.B"

    Test->>Test: 解析错误信息
    Test->>Test: 从 source_map[20] 获取源代码
    Test-->>User: 显示详细错误报告

错误信息传递流程

flowchart LR
    A[VBA 运行时错误] --> B[On Error GoTo 捕获]
    B --> C[跳转到 Auto_Err_Handler]
    C --> D[调用 TestLogger.LogError]

    D --> E[设置 m_TestStatus = ERROR]
    E --> F[保存错误信息:<br/>模块、过程、行号、编号、描述]
    F --> G[从 CallStack 获取调用链]

    G --> H[TestRunner 调用 GetResult]
    H --> I{检查 m_TestStatus}

    I -->|SUCCESS| J[返回 "SUCCESS"]
    I -->|ERROR| K[构造错误字符串:<br/>ERROR + 模块.过程 + 行号 + 编号 + 描述 + 调用链]

    K --> L[TestRunner 解析字符串]
    L --> M[提取错误模块、过程、行号]
    M --> N[从 source_map 查找源代码]
    N --> O[构造 TestResult 对象]
    O --> P[显示详细错误报告]

    style A fill:#ffcdd2
    style J fill:#c8e6c9
    style K fill:#ffccbc
    style P fill:#fff9c4

类关系图

系统类结构与依赖关系

classDiagram
    class TestResult {
        +str procedure_name
        +bool success
        +int error_number
        +str error_description
        +int error_line
        +str source_code
        +str error_module
        +str call_chain
    }

    class CodeWeaver {
        -int line_counter
        +parse_modules(vba_project) Dict
        +parse_procedures(code) Dict
        +weave_procedure_with_callstack(code, proc_name, module_name) Tuple
        +weave_module_all_procedures(code, module_name) Tuple
        +weave_all_modules(modules) Dict
        -_inject_line_numbers(lines) Tuple
        -_inject_error_handler_with_callstack(lines, proc_name, module_name) List
        -_get_component_code(component) str
    }

    class LoggerInjector {
        +str CALLSTACK_CLASS_CODE
        +str LOGGER_MODULE_CODE
        +inject_or_replace(wb, module_name) None
    }

    class TestRunner {
        +str file_path
        +bool visible
        +CodeWeaver code_weaver
        +LoggerInjector logger_injector
        +Dict source_map
        +run_test(module_name, proc_name) TestResult
        +run_all_tests() List~TestResult~
        +discover_all_tests() List~Dict~
        -_get_vba_code(module_name) str
        -_replace_module_code(module_name, new_code) None
        -_execute_macro(proc_name) None
        -_get_test_result(proc_name) TestResult
        -_cleanup() None
    }

    class TestLogger {
        <<VBA Module>>
        +LogEntry(procName, moduleName)
        +LogExit()
        +LogError(procName, moduleName, errNum, errDesc, errLine)
        +LogSuccess()
        +GetResult() String
        +Initialize()
    }

    class CallStack {
        <<VBA Class Module>>
        -Collection m_Stack
        +Push(procName, moduleName)
        +Pop()
        +GetCallChain() String
        +Clear()
    }

    TestRunner --> CodeWeaver : 使用
    TestRunner --> LoggerInjector : 使用
    TestRunner --> TestResult : 创建
    LoggerInjector --> TestLogger : 注入
    LoggerInjector --> CallStack : 注入
    TestLogger --> CallStack : 使用
    CodeWeaver --> TestResult : 生成 source_map

模块交互时序图

sequenceDiagram
    participant User as 用户
    participant Main as main()
    participant Runner as TestRunner
    participant Weaver as CodeWeaver
    participant Injector as LoggerInjector
    participant Excel as Excel Application

    User->>Main: 执行命令
    Main->>Runner: 初始化(file_path, visible=False)

    alt 单个测试模式
        Main->>Runner: run_test(module, proc)
    else 全项目测试模式
        Main->>Runner: run_all_tests()
        Runner->>Runner: discover_all_tests()
        Runner->>Runner: _weave_all_modules_inplace()
    end

    Runner->>Excel: 打开工作簿
    Runner->>Weaver: parse_modules(vba_project)
    Weaver-->>Runner: 模块信息

    Runner->>Weaver: weave_module_all_procedures(code, module)
    Weaver->>Weaver: _inject_line_numbers()
    Weaver->>Weaver: _inject_error_handler_with_callstack()
    Weaver-->>Runner: woven_code, source_map

    Runner->>Injector: inject_or_replace(wb)
    Injector->>Excel: 删除旧的 TestLogger/CallStack
    Injector->>Excel: 添加新的 CallStack 类模块
    Injector->>Excel: 添加新的 TestLogger 模块

    Runner->>Excel: _replace_module_code(module, woven_code)
    Runner->>Excel: _execute_macro(module.proc)
    Excel-->>Runner: 执行结果

    Runner->>Excel: TestLogger.GetResult()
    Excel-->>Runner: "SUCCESS" 或 "ERROR|..."

    Runner-->>Main: TestResult
    Main-->>User: 打印测试结果

    Runner->>Excel: _cleanup()
    Excel->>Excel: 关闭工作簿(不保存)
    Excel->>Excel: 退出 Excel

附录:关键数据结构

TestResult 数据类

graph TD
    A[TestResult 数据类] --> B[procedure_name: str<br/>被测试的过程名称]
    A --> C[success: bool<br/>测试是否成功]
    A --> D[error_number: int<br/>VBA 错误编号]
    A --> E[error_description: str<br/>错误描述信息]
    A --> F[error_line: int<br/>错误发生的行号标签]
    A --> G[source_code: str<br/>错误行的原始源代码]
    A --> H[error_module: str<br/>错误发生的模块名]
    A --> I[call_chain: str<br/>完整调用链<br/>Module1.ProcA -> Module1.ProcB]

    style A fill:#e1f5ff
    style C fill:#c8e6c9
    style F fill:#fff9c4
    style G fill:#fff9c4
    style I fill:#ffccbc

Source Map 映射关系

graph LR
    A[原始 VBA 代码] -->|CodeWeaver 处理| B[编织后的代码]

    B --> C[行号标签 10<br/>x = 1]
    B --> D[行号标签 20<br/>y = x / 0  ❌]
    B --> E[行号标签 30<br/>z = y + 1]

    C --> F[source_map[10] = 'x = 1']
    D --> G[source_map[20] = 'y = x / 0']
    E --> H[source_map[30] = 'z = y + 1']

    G --> I[VBA 错误发生在行 20]
    I --> J[通过 source_map[20]<br/>获取原始源代码]
    J --> K[显示给用户:<br/>y = x / 0]

    style A fill:#e1f5ff
    style B fill:#c8e6c9
    style G fill:#ffcdd2
    style K fill:#fff9c4

使用示例流程

命令行使用模式

flowchart TD
    Start([用户执行命令]) --> CheckArgs{参数检查}

    CheckArgs -->|python vba_test_runner.py| ShowHelp1[显示用法1]
    CheckArgs -->|python vba_test_runner.py file.xlsm --all| AllTest[全项目测试模式]
    CheckArgs -->|python vba_test_runner.py file.xlsm Module Proc1 Proc2| SingleTest[单过程/多过程测试模式]

    ShowHelp1 --> End1([结束])

    AllTest --> InitRunner1[初始化 TestRunner<br/>visible=False]
    InitRunner1 --> RunAll[执行 run_all_tests]
    RunAll --> Discover[发现所有过程]
    Discover --> WeaveAllModules[编织所有模块]
    WeaveAllModules --> BatchExecute[批量执行测试]
    BatchExecute --> ShowSummary[显示测试汇总]
    ShowSummary --> Cleanup1[清理资源]
    Cleanup1 --> End2([结束])

    SingleTest --> InitRunner2[初始化 TestRunner<br/>visible=False]
    InitRunner2 --> LoopProcs[循环遍历过程列表]
    LoopProcs --> RunSingle[执行 run_test]
    RunSingle --> ShowSingleResult[显示单个结果]
    ShowSingleResult --> MoreProcs{更多过程?}
    MoreProcs -->|是| LoopProcs
    MoreProcs -->|否| ShowSummary2[显示测试汇总]
    ShowSummary2 --> Cleanup2[清理资源]
    Cleanup2 --> End3([结束])

    style Start fill:#e1f5ff
    style End1 fill:#e1f5ff
    style End2 fill:#e1f5ff
    style End3 fill:#e1f5ff
    style AllTest fill:#c8e6c9
    style SingleTest fill:#b3e5fc

总结

该 VBA 自动化测试系统通过代码编织技术实现了:

  1. 非侵入式测试:在内存中修改代码,不保存到磁盘
  2. 精确行号定位:通过行号标签和 Source Map 实现源代码映射
  3. 调用链追踪:通过 CallStack 类追踪完整调用路径
  4. 批量测试支持:支持单过程和全项目两种测试模式
  5. 详细错误报告:包含错误位置、源代码、调用链等完整信息

系统的核心创新在于将编译器中的代码编织技术应用于 VBA 动态测试,通过在运行时注入检测代码来实现传统 IDE 无法提供的调试功能。