datai/datai-scenes/datai-scene-salesforce/docs/design/2026-02-02-002-03-测试执行-设计.md

27 KiB
Raw Permalink Blame History

设计文档 - 测试执行

元数据

  • 需求编号002-03
  • 创建时间2026-02-02
  • 创建人AI Assistant
  • 状态:已完成(代码已生成)

设计概述

本设计文档描述 Apex 测试执行功能的完整技术方案。测试执行功能允许用户在 Salesforce 平台上运行 Apex 测试,支持多种运行方式(全部测试、指定类、指定包、指定方法),并提供详细的测试结果、代码覆盖率和 Flow 覆盖率分析。

设计目标

  1. 提供灵活的测试执行方式(全部、按类、按包、按方法)
  2. 详细记录测试结果(成功、失败、覆盖率)
  3. 支持代码覆盖率和 Flow 覆盖率分析
  4. 确保多线程安全和数据一致性
  5. 提供 RESTful API 接口供外部调用

与相关功能的关系

  • 依赖 002-01连接管理:使用 ApexConnectionFactory 获取 SoapConnection
  • 复用 002-02编译和执行:复用 ApexTestResult、ApexTestFailure、ApexCodeCoverage 表
  • 新建表ApexTestSuccess测试成功详情、ApexFlowCoverageFlow 覆盖率)

架构设计

系统架构

┌─────────────────────────────────────────────────────────────┐
│                        客户端层                              │
│              (Vue 3 + Element Plus / API 调用)              │
└───────────────────────┬─────────────────────────────────────┘
                        │ HTTP/HTTPS
┌───────────────────────▼─────────────────────────────────────┐
│                      Controller 层                           │
│  ┌──────────────┐ ┌──────────────┐ ┌──────────────┐        │
│  │ApexTest      │ │ApexTest      │ │ApexTest      │        │
│  │Controller    │ │Query         │ │Coverage      │        │
│  │(执行测试)     │ │Controller    │ │Controller    │        │
│  └──────────────┘ │(查询结果)     │ │(查询覆盖率)   │        │
│                   └──────────────┘ └──────────────┘        │
└───────────────────────┬─────────────────────────────────────┘
                        │
┌───────────────────────▼─────────────────────────────────────┐
│                       Service 层                             │
│  ┌──────────────────────────────────────────────────────┐  │
│  │              IApexTestService                          │  │
│  │  - runAllTests()                                       │  │
│  │  - runTestsByClasses(String[] classes)                 │  │
│  │  - runTestsByPackages(String[] packages)               │  │
│  │  - runTestsByMethods(TestsNode[] tests)                │  │
│  │  - runTests(RunTestsRequest request)                   │  │
│  │  - queryTestResults(...)                               │  │
│  │  - queryTestDetails(...)                               │  │
│  │  - queryCodeCoverage(...)                              │  │
│  │  - queryFlowCoverage(...)                              │  │
│  └──────────────────────────────────────────────────────┘  │
│  ┌──────────────────────────────────────────────────────┐  │
│  │              ApexTestServiceImpl                       │  │
│  │  - ReentrantLock 保证多线程安全                         │  │
│  │  - @Transactional 保证数据一致性                        │  │
│  │  - 保存测试结果到数据库                                  │  │
│  └──────────────────────────────────────────────────────┘  │
└───────────────────────┬─────────────────────────────────────┘
                        │
┌───────────────────────▼─────────────────────────────────────┐
│                      Factory 层                              │
│  ┌──────────────────────────────────────────────────────┐  │
│  │              ApexConnectionFactory                     │  │
│  │  - getConnection(String orgType)                       │  │
│  │  - 复用 002-01 的连接管理机制                           │  │
│  └──────────────────────────────────────────────────────┘  │
└───────────────────────┬─────────────────────────────────────┘
                        │
┌───────────────────────▼─────────────────────────────────────┐
│                    Salesforce API 层                         │
│  ┌──────────────────────────────────────────────────────┐  │
│  │              SoapConnection (apex.jar)                 │  │
│  │  - runTests(RunTestsRequest request)                   │  │
│  │  - RunTestsResult / RunTestSuccess / RunTestFailure    │  │
│  │  - CodeCoverageResult / FlowCoverageResult             │  │
│  └──────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘

模块架构

模块位置datai-salesforce-apex 模块

包结构

com.datai.apex
├── controller
│   ├── ApexTestController.java       # 测试执行接口
│   ├── ApexTestQueryController.java  # 测试结果查询接口
│   └── ApexTestCoverageController.java # 覆盖率查询接口
├── service
│   ├── IApexTestService.java         # 测试服务接口
│   └── impl
│       └── ApexTestServiceImpl.java  # 测试服务实现
├── model
│   ├── dto
│   │   ├── RunAllTestsDto.java       # 运行所有测试请求
│   │   ├── RunTestsByClassesDto.java # 按类运行测试请求
│   │   ├── RunTestsByPackagesDto.java # 按包运行测试请求
│   │   ├── RunTestsByMethodsDto.java # 按方法运行测试请求
│   │   └── QueryTestResultsDto.java  # 查询测试结果请求
│   └── vo
│       ├── RunTestsResultVo.java     # 测试结果 VO
│       ├── RunTestSuccessVo.java     # 测试成功 VO
│       ├── RunTestFailureVo.java     # 测试失败 VO
│       ├── CodeCoverageResultVo.java # 代码覆盖率 VO
│       └── FlowCoverageResultVo.java # Flow 覆盖率 VO
└── mapper
    ├── DataiApexTestSuccessMapper.java # 测试成功详情 Mapper新建
    └── DataiApexFlowCoverageMapper.java # Flow 覆盖率 Mapper新建

数据流

1. 客户端发送测试执行请求
   ↓
2. Controller 接收请求,参数校验
   ↓
3. Service 获取 SoapConnection复用 002-01
   ↓
4. Service 调用 Salesforce API 执行测试
   ↓
5. Service 解析测试结果RunTestsResult
   ↓
6. Service 保存测试结果到数据库5 张表)
   ↓
7. Service 返回测试结果给 Controller
   ↓
8. Controller 返回统一响应格式给客户端

技术方案

技术选型

技术组件 选型 说明
连接管理 ApexConnectionFactory 复用 002-01 的连接管理机制
多线程安全 ReentrantLock 保证测试执行的多线程安全
事务管理 @Transactional 保证测试结果保存的数据一致性
权限控制 Spring Security @PreAuthorize 接口级别的权限控制
数据持久化 MyBatis 使用标准 MyBatis非 MyBatis Plus
API 文档 Swagger 自动生成 API 文档

核心算法

1. 测试结果保存算法

@Transactional(rollbackFor = Exception.class)
public void saveTestResults(RunTestsResult result) {
    // 1. 保存测试结果主表
    DataiApexTestResult testResult = convertToTestResult(result);
    testResultMapper.insert(testResult);
    
    // 2. 保存测试成功详情
    if (result.getSuccesses() != null) {
        for (RunTestSuccess success : result.getSuccesses()) {
            DataiApexTestSuccess testSuccess = convertToTestSuccess(success, testResult.getId());
            testSuccessMapper.insert(testSuccess);
        }
    }
    
    // 3. 保存测试失败详情
    if (result.getFailures() != null) {
        for (RunTestFailure failure : result.getFailures()) {
            DataiApexTestFailure testFailure = convertToTestFailure(failure, testResult.getId());
            testFailureMapper.insert(testFailure);
        }
    }
    
    // 4. 保存代码覆盖率
    if (result.getCodeCoverage() != null) {
        for (CodeCoverageResult coverage : result.getCodeCoverage()) {
            DataiApexCodeCoverage codeCoverage = convertToCodeCoverage(coverage, testResult.getId());
            codeCoverageMapper.insert(codeCoverage);
        }
    }
    
    // 5. 保存 Flow 覆盖率
    if (result.getFlowCoverage() != null) {
        for (FlowCoverageResult flowCoverage : result.getFlowCoverage()) {
            DataiApexFlowCoverage flowCov = convertToFlowCoverage(flowCoverage, testResult.getId());
            flowCoverageMapper.insert(flowCov);
        }
    }
}

2. 覆盖率计算算法

// 代码覆盖率计算
double coveragePercent = (1.0 - (double) numLocationsNotCovered / numLocations) * 100;

// Flow 覆盖率计算
double flowCoveragePercent = (double) numElementsCovered / numElements * 100;

集成方案

与 Salesforce API 集成

// 1. 构建 RunTestsRequest
RunTestsRequest request = new RunTestsRequest();
request.setAllTests(true);  // 或设置 classes/packages/tests
request.setSkipCodeCoverage(false);
request.setMaxFailedTests(0);

// 2. 获取连接并执行测试
SoapConnection connection = apexConnectionFactory.getConnection("source");
RunTestsResult result = connection.runTests(request);

// 3. 解析结果
int numTestsRun = result.getNumTestsRun();
int numFailures = result.getNumFailures();
RunTestSuccess[] successes = result.getSuccesses();
RunTestFailure[] failures = result.getFailures();
CodeCoverageResult[] codeCoverage = result.getCodeCoverage();
FlowCoverageResult[] flowCoverage = result.getFlowCoverage();

数据模型

数据库表设计

1. datai_apex_test_result测试结果表- 复用 002-02

字段名 类型 长度 是否必填 默认值 说明
id BIGINT - 自增 主键
test_run_id VARCHAR 50 - 测试运行 ID
num_tests_run INT - 0 运行的测试数量
num_failures INT - 0 失败的测试数量
total_time DOUBLE - 0 总执行时间(毫秒)
apex_log_id VARCHAR 50 - Apex 日志 ID
test_time DATETIME - - 测试时间
all_passed BOOLEAN - false 是否全部通过
total_coverage DOUBLE - 0 总体覆盖率
dept_id BIGINT - 0 部门 ID
create_by VARCHAR 64 - 创建者
create_time DATETIME - CURRENT_TIMESTAMP 创建时间
update_by VARCHAR 64 - 更新者
update_time DATETIME - CURRENT_TIMESTAMP 更新时间
del_flag TINYINT - 0 删除标志

索引

  • PRIMARY KEY (id)
  • INDEX idx_test_run_id (test_run_id)
  • INDEX idx_test_time (test_time)
  • INDEX idx_create_by (create_by)
  • INDEX idx_dept_id (dept_id)

2. datai_apex_test_success测试成功详情表- 新建

字段名 类型 长度 是否必填 默认值 说明
id BIGINT - 自增 主键
test_result_id BIGINT - - 测试结果 ID外键
test_id VARCHAR 50 - 测试 ID
class_name VARCHAR 255 - 类名
method_name VARCHAR 255 - 方法名
time DOUBLE - 0 执行时间(毫秒)
message TEXT - - 消息
stack_trace TEXT - - 堆栈跟踪
create_time DATETIME - CURRENT_TIMESTAMP 创建时间
del_flag TINYINT - 0 删除标志

索引

  • PRIMARY KEY (id)
  • INDEX idx_test_result_id (test_result_id)
  • INDEX idx_class_name (class_name)
  • INDEX idx_method_name (method_name)

3. datai_apex_test_failure测试失败详情表- 复用 002-02

字段名 类型 长度 是否必填 默认值 说明
id BIGINT - 自增 主键
test_result_id BIGINT - - 测试结果 ID外键
test_id VARCHAR 50 - 测试 ID
class_name VARCHAR 255 - 类名
method_name VARCHAR 255 - 方法名
message TEXT - - 失败消息
stack_trace TEXT - - 堆栈跟踪
type VARCHAR 255 - 异常类型
time DOUBLE - 0 执行时间(毫秒)
create_time DATETIME - CURRENT_TIMESTAMP 创建时间
del_flag TINYINT - 0 删除标志

索引

  • PRIMARY KEY (id)
  • INDEX idx_test_result_id (test_result_id)
  • INDEX idx_class_name (class_name)
  • INDEX idx_method_name (method_name)

4. datai_apex_code_coverage代码覆盖率表- 复用 002-02

字段名 类型 长度 是否必填 默认值 说明
id BIGINT - 自增 主键
test_result_id BIGINT - - 测试结果 ID外键
name VARCHAR 255 - 类/触发器名称
type VARCHAR 20 - 类型Class/Trigger
namespace VARCHAR 255 - 命名空间
num_locations INT - 0 总位置数
num_locations_not_covered INT - 0 未覆盖的位置数
coverage_percent DOUBLE - 0 覆盖率百分比
create_time DATETIME - CURRENT_TIMESTAMP 创建时间
del_flag TINYINT - 0 删除标志

索引

  • PRIMARY KEY (id)
  • INDEX idx_test_result_id (test_result_id)
  • INDEX idx_name (name)
  • INDEX idx_type (type)

5. datai_apex_flow_coverageFlow 覆盖率表)- 新建

字段名 类型 长度 是否必填 默认值 说明
id BIGINT - 自增 主键
test_result_id BIGINT - - 测试结果 ID外键
name VARCHAR 255 - Flow 名称
type VARCHAR 50 - Flow 类型
namespace VARCHAR 255 - 命名空间
num_elements INT - 0 总元素数量
num_elements_covered INT - 0 覆盖的元素数量
coverage_percent DOUBLE - 0 覆盖率百分比
create_time DATETIME - CURRENT_TIMESTAMP 创建时间
del_flag TINYINT - 0 删除标志

索引

  • PRIMARY KEY (id)
  • INDEX idx_test_result_id (test_result_id)
  • INDEX idx_name (name)
  • INDEX idx_type (type)

实体类设计

1. DataiApexTestSuccess测试成功详情实体- 新建

@Data
public class DataiApexTestSuccess {
    private Long id;
    private Long testResultId;
    private String testId;
    private String className;
    private String methodName;
    private Double time;
    private String message;
    private String stackTrace;
    private Date createTime;
    private Integer delFlag;
}

2. DataiApexFlowCoverageFlow 覆盖率实体)- 新建

@Data
public class DataiApexFlowCoverage {
    private Long id;
    private Long testResultId;
    private String name;
    private String type;
    private String namespace;
    private Integer numElements;
    private Integer numElementsCovered;
    private Double coveragePercent;
    private Date createTime;
    private Integer delFlag;
}

接口设计

RESTful API 设计

1. 运行所有测试

  • 接口名称runAllTests
  • 请求方式POST
  • 请求路径/api/apex/test/run-all
  • 权限要求@PreAuthorize("@ss.hasPermi('apex:test:execute')")
  • 请求参数
    • skipCodeCoverage (Boolean, 可选):是否跳过代码覆盖率检查,默认 false
    • maxFailedTests (Integer, 可选):最大失败测试数,默认 0不限制
  • 响应数据RunTestsResultVo

2. 运行指定类的测试

  • 接口名称runTestsByClasses
  • 请求方式POST
  • 请求路径/api/apex/test/run-by-classes
  • 权限要求@PreAuthorize("@ss.hasPermi('apex:test:execute')")
  • 请求参数
    • classes (String[], 必填):要测试的类名称数组
    • skipCodeCoverage (Boolean, 可选):是否跳过代码覆盖率检查,默认 false
    • maxFailedTests (Integer, 可选):最大失败测试数,默认 0
  • 响应数据RunTestsResultVo

3. 运行指定包的测试

  • 接口名称runTestsByPackages
  • 请求方式POST
  • 请求路径/api/apex/test/run-by-packages
  • 权限要求@PreAuthorize("@ss.hasPermi('apex:test:execute')")
  • 请求参数
    • namespace (String, 可选):命名空间
    • packages (String[], 必填):要测试的包数组
    • skipCodeCoverage (Boolean, 可选):是否跳过代码覆盖率检查,默认 false
    • maxFailedTests (Integer, 可选):最大失败测试数,默认 0
  • 响应数据RunTestsResultVo

4. 运行指定测试方法

  • 接口名称runTestsByMethods
  • 请求方式POST
  • 请求路径/api/apex/test/run-by-methods
  • 权限要求@PreAuthorize("@ss.hasPermi('apex:test:execute')")
  • 请求参数
    • tests (TestsNodeDto[], 必填):要运行的测试方法数组
    • skipCodeCoverage (Boolean, 可选):是否跳过代码覆盖率检查,默认 false
    • maxFailedTests (Integer, 可选):最大失败测试数,默认 0
  • 响应数据RunTestsResultVo

5. 查询测试结果

  • 接口名称queryTestResults
  • 请求方式GET
  • 请求路径/api/apex/test/results
  • 权限要求@PreAuthorize("@ss.hasPermi('apex:test:query')")
  • 请求参数
    • testRunId (String, 可选):测试运行 ID
    • startTime (String, 可选)开始时间yyyy-MM-dd HH:mm:ss
    • endTime (String, 可选)结束时间yyyy-MM-dd HH:mm:ss
    • pageNum (Integer, 可选):页码,默认 1
    • pageSize (Integer, 可选):每页大小,默认 10
  • 响应数据PageResult

6. 查询测试详情

  • 接口名称queryTestDetails
  • 请求方式GET
  • 请求路径/api/apex/test/details
  • 权限要求@PreAuthorize("@ss.hasPermi('apex:test:query')")
  • 请求参数
    • testResultId (Long, 必填):测试结果 ID
    • type (String, 可选)类型success/failure/all默认 all
    • pageNum (Integer, 可选):页码,默认 1
    • pageSize (Integer, 可选):每页大小,默认 20
  • 响应数据TestDetailsVo包含 successes 和 failures

7. 查询代码覆盖率

  • 接口名称queryCodeCoverage
  • 请求方式GET
  • 请求路径/api/apex/test/coverage
  • 权限要求@PreAuthorize("@ss.hasPermi('apex:test:query')")
  • 请求参数
    • testResultId (Long, 可选):测试结果 ID
    • name (String, 可选):类/触发器名称
    • type (String, 可选)类型Class/Trigger
    • minCoverage (Double, 可选):最小覆盖率
    • maxCoverage (Double, 可选):最大覆盖率
    • pageNum (Integer, 可选):页码,默认 1
    • pageSize (Integer, 可选):每页大小,默认 20
  • 响应数据PageResult

8. 查询 Flow 覆盖率

  • 接口名称queryFlowCoverage
  • 请求方式GET
  • 请求路径/api/apex/test/flow-coverage
  • 权限要求@PreAuthorize("@ss.hasPermi('apex:test:query')")
  • 请求参数
    • testResultId (Long, 可选):测试结果 ID
    • name (String, 可选)Flow 名称
    • type (String, 可选)Flow 类型
    • minCoverage (Double, 可选):最小覆盖率
    • maxCoverage (Double, 可选):最大覆盖率
    • pageNum (Integer, 可选):页码,默认 1
    • pageSize (Integer, 可选):每页大小,默认 20
  • 响应数据PageResult

接口权限设计

接口 权限标识 说明
POST /api/apex/test/run-all apex:test:execute 执行测试权限
POST /api/apex/test/run-by-classes apex:test:execute 执行测试权限
POST /api/apex/test/run-by-packages apex:test:execute 执行测试权限
POST /api/apex/test/run-by-methods apex:test:execute 执行测试权限
GET /api/apex/test/results apex:test:query 查询测试结果权限
GET /api/apex/test/details apex:test:query 查询测试详情权限
GET /api/apex/test/coverage apex:test:query 查询代码覆盖率权限
GET /api/apex/test/flow-coverage apex:test:query 查询 Flow 覆盖率权限

实现要点

关键实现逻辑

1. 测试执行流程

@Override
@Transactional(rollbackFor = Exception.class)
public RunTestsResultVo runTests(RunTestsRequestDto dto) throws SalesforceAuthException, SalesforceOperationException {
    lock.lock();
    try {
        // 1. 构建 RunTestsRequest
        RunTestsRequest request = buildRunTestsRequest(dto);
        
        // 2. 获取 SoapConnection
        SoapConnection connection = apexConnectionFactory.getConnection("source");
        
        // 3. 执行测试
        RunTestsResult result = connection.runTests(request);
        
        // 4. 保存测试结果
        saveTestResults(result);
        
        // 5. 转换为 VO 并返回
        return convertToVo(result);
        
    } catch (SalesforceAuthException e) {
        throw e;
    } catch (Exception e) {
        throw new SalesforceOperationException("运行测试失败: " + e.getMessage(), e);
    } finally {
        lock.unlock();
    }
}

2. 测试结果保存流程

private void saveTestResults(RunTestsResult result) {
    // 1. 保存测试结果主表
    DataiApexTestResult testResult = new DataiApexTestResult();
    testResult.setTestRunId(result.getApexLogId()); // 使用 apexLogId 作为 testRunId
    testResult.setNumTestsRun(result.getNumTestsRun());
    testResult.setNumFailures(result.getNumFailures());
    testResult.setTotalTime(result.getTotalTime());
    testResult.setApexLogId(result.getApexLogId());
    testResult.setTestTime(new Date());
    testResult.setAllPassed(result.getNumFailures() == 0);
    
    // 计算总体覆盖率
    double totalCoverage = calculateTotalCoverage(result.getCodeCoverage());
    testResult.setTotalCoverage(totalCoverage);
    
    testResultMapper.insert(testResult);
    
    // 2. 保存测试成功详情
    saveTestSuccesses(result.getSuccesses(), testResult.getId());
    
    // 3. 保存测试失败详情
    saveTestFailures(result.getFailures(), testResult.getId());
    
    // 4. 保存代码覆盖率
    saveCodeCoverages(result.getCodeCoverage(), testResult.getId());
    
    // 5. 保存 Flow 覆盖率
    saveFlowCoverages(result.getFlowCoverage(), testResult.getId());
}

异常处理

1. 异常分类

异常类型 说明 处理方式
SalesforceAuthException Salesforce 认证失败 抛出,由全局异常处理器处理
SalesforceOperationException Salesforce 操作失败 抛出,由全局异常处理器处理
IllegalArgumentException 参数校验失败 抛出,返回 400 错误
Exception 其他异常 转换为 SalesforceOperationException

2. 异常处理示例

try {
    // 执行测试
} catch (ConnectionException e) {
    log.error("Salesforce 连接失败: {}", e.getMessage());
    throw new SalesforceAuthException("Salesforce 连接失败: " + e.getMessage(), e);
} catch (Exception e) {
    log.error("运行测试失败: {}", e.getMessage(), e);
    throw new SalesforceOperationException("运行测试失败: " + e.getMessage(), e);
}

性能优化

1. 数据库优化

  • 索引优化为所有查询字段建立索引test_run_id、test_time、class_name、method_name 等)
  • 分页查询:使用分页查询避免大数据量查询导致的性能问题
  • 批量插入:使用 MyBatis 的批量插入功能提高插入性能

2. 并发控制

  • ReentrantLock:使用 ReentrantLock 保证测试执行的多线程安全
  • 连接池:复用 ApexConnectionFactory 的连接池管理

安全设计

1. 权限控制

  • 接口级别:使用 @PreAuthorize 进行接口级别的权限控制
  • 数据级别:使用 dept_id 进行数据隔离

2. 数据验证

  • 参数校验:使用 @Validated 进行参数校验
  • SQL 注入防护:使用 MyBatis 的参数化查询

相关文档