You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ProcessBatch测试类覆盖率不足求助:含金额条件时覆盖率仅35%

问题:批处理类添加业务条件后测试覆盖率骤降的解决方法

背景

我开发了一个实现Database.Batchable、Database.AllowsCallouts、Schedulable接口的ProcessBatch批处理类,当加入&& doc.Amount >= 50000的业务条件后,测试类覆盖率从80%降至35%,该条件符合业务需求。

批处理类代码(已修正明显语法错误)

public class ProcessBatch implements Database.Batchable<Object>, Database.AllowsCallouts, Schedulable{
    public String AccountURL;
    public String ProcessURL;
    public String Key;

    private class JsonUpsertResult {
        List<Database.Error> Error {get;set;}
        String SFDCId{get;set;}
        String visibleID{get;set;}
        Boolean isCreated {get;set;}
        Boolean isSuccess {get;set;}
    }

    public ProcessBatch(){
        SAP_Details__c SAP = SAP_Details__c.getOrgDefaults();
        AccountURL = SAP.Account_API_URL__c;
        ProcessURL = SAP.Process_API_URL__c;
        Key = SAP.API_Key__c;
        System.debug('API:' + ProcessURL + '-' + Key);
    }

    public Iterable<Object> start(Database.BatchableContext BC) {
        List<Object> results = new List<Object>();
        
        Http http = new Http();
        HttpRequest request = new HttpRequest();
        System.debug('API:' + ProcessURL + '-' + Key);
        request.setEndpoint(ProcessURL);
        request.setMethod('POST');
        request.setHeader('Authorization', Key);
        request.setHeader('Accept', '*/*');
        request.setHeader('content-type', 'application/json');

        request.setBody(filters);
        HttpResponse response = http.send(request);
        
        if(response.getStatusCode() == 200){
            SAPProcessJSON2Apex gt = (SAPProcessJSON2Apex) JSON.deserialize(response.getBody(), SAPProcessJSON2Apex.class);
            List<SAPProcessJSON2Apex.Result> res = gt.result;
            System.debug('Size of Result: ' + res.size());
            results = res;
        }
        return results;
    }

    public void execute(Database.BatchableContext BC, List<Object> scope){
        List<JSON2Apex.Results> accountResults = new List<JSON2Apex.Results>();
        List<Process__c> ProcessToUpsert = new List<Process__c>();
        List<Process_Agent__c> CollectivesToupsert = new List<Process_Agent__c>();
        List<Department__c> departmentToupdate = new List<Department__c>();
        List<JSON2Apex.Results> revisedAccRes = new List<JSON2Apex.Results>();
        List<SAPProcessJSON2Apex.Result> res = (List<SAPProcessJSON2Apex.Result>) scope;

        List<SAPProcessJSON2Apex.Result> revisedRes = new List<SAPProcessJSON2Apex.Result>();

        for(SAPProcessJSON2Apex.Result r : res) {
            SAPProcessJSON2Apex.Document doc = r.document;
            List<SAPProcessJSON2Apex.Companies> comp = doc.companies;
            List<String> processTypes = doc.processTypes;
            List<String> categories = doc.categories;

            Process__c pr = new Process__c();
            pr.Visibleprocess_No__c = doc.processId;
            pr.Visible_method__c = 'SAP';
            pr.process_Description__c = doc.description;
            pr.Amount__c = doc.Amount; 
            pr.Construction_Type__c = doc.constructionType;

            if(doc.phase == 'First planning'){
                pr.Division__c = 'First planning';
            } else if(doc.phase == 'Quotation'){
                pr.Division__c = 'Main Step Quotation'; 
            } else if(doc.phase == 'Contract Get') {
                pr.Division__c = 'Main Step Agricultural';
            }

            pr.process_Full_Name__c = doc.title; 
            pr.Name = doc.title.left(80);

            if(categories.size() > 0){
                if(categories[0] == 'Factory' || categories[0] == 'Storage'){
                    pr.Sector__c = 'Industrial';
                } else if(categories[0] == 'Packaging/Storage') {
                    pr.Type_of_Sector__c = 'Warehouse'; 
                } else if(categories[0] == 'Shops and Retail' || categories[0] == 'Any Showrooms') {
                    pr.Sector__c = 'Commercial'; 
                }

                if(pr.Sector__c != null && doc.Amount >= 50000){
                    ProcessToUpsert.add(pr); 
                    revisedRes.add(r);
                } 
            } 
        }
    }

    public void finish(Database.BatchableContext BC) {
    }

    public void execute(SchedulableContext sc) {
        Database.executeBatch(new ProcessBatch(), 30);
    }
}

现有测试类代码

@isTest(SeeAllData = true) 
public class ProcessBatchTest {
    @isTest public static void processBatchTest() {
        Test.setMock(HttpCalloutMock.class, new HttpMock()); 
        ProcessBatch pb = new ProcessBatch(); 
        Test.startTest(); 
        Database.executeBatch(pb, 30); 
        Test.stopTest();
    }
}

解决方案

1. 完善HttpMock的模拟数据

当前测试用的HttpMock没返回满足业务条件的数据集,导致execute里大部分分支没被覆盖。需要修改HttpMock,生成覆盖所有业务分支的模拟JSON:

@isTest
public class HttpMock implements HttpCalloutMock {
    public HttpResponse respond(HttpRequest req) {
        HttpResponse res = new HttpResponse();
        res.setStatusCode(200);
        // 构造覆盖所有场景的模拟数据
        String jsonBody = '{ "result": [' +
            // 满足条件:Industrial领域、金额≥50000、First planning阶段
            '{ "document": { "processId": "PROC-001", "description": "Test Industrial", "Amount": 60000, "constructionType": "New", "phase": "First planning", "title": "Factory Project", "categories": ["Factory"], "companies": [] } },' +
            // 满足条件:Warehouse领域、金额≥50000、Quotation阶段
            '{ "document": { "processId": "PROC-002", "description": "Test Warehouse", "Amount": 75000, "constructionType": "Renovate", "phase": "Quotation", "title": "Storage Project", "categories": ["Packaging/Storage"], "companies": [] } },' +
            // 满足条件:Commercial领域、金额≥50000、Contract Get阶段
            '{ "document": { "processId": "PROC-003", "description": "Test Commercial", "Amount": 55000, "constructionType": "New", "phase": "Contract Get", "title": "Retail Project", "categories": ["Shops and Retail"], "companies": [] } },' +
            // 不满足条件:领域不匹配、金额≥50000
            '{ "document": { "processId": "PROC-004", "description": "Test No Sector", "Amount": 60000, "constructionType": "New", "phase": "First planning", "title": "Uncategorized", "categories": ["Other"], "companies": [] } },' +
            // 不满足条件:金额<50000、领域匹配
            '{ "document": { "processId": "PROC-005", "description": "Test Low Amount", "Amount": 40000, "constructionType": "New", "phase": "First planning", "title": "Small Project", "categories": ["Factory"], "companies": [] } }' +
        '] }';
        res.setBody(jsonBody);
        return res;
    }
}

2. 优化测试类的断言逻辑

在测试方法里添加断言,既验证业务逻辑正确性,也确保代码分支被覆盖:

@isTest(SeeAllData = false) // 禁用SeeAllData,避免依赖生产数据
public class ProcessBatchTest {
    @isTest public static void processBatchTest() {
        // 提前创建测试用的SAP配置数据
        SAP_Details__c testSAP = new SAP_Details__c(
            Process_API_URL__c = 'https://test-api.com/process',
            API_Key__c = 'test-key'
        );
        insert testSAP;

        Test.setMock(HttpCalloutMock.class, new HttpMock()); 
        ProcessBatch pb = new ProcessBatch(); 
        Test.startTest(); 
        Database.executeBatch(pb, 30); 
        Test.stopTest();

        // 验证符合条件的Process记录数量
        List<Process__c> createdProcesses = [SELECT Id, Sector__c, Type_of_Sector__c FROM Process__c WHERE Visible_method__c = 'SAP'];
        System.assertEquals(3, createdProcesses.size(), '应创建3条符合条件的Process记录');
        
        // 验证各分支是否被覆盖
        Boolean hasIndustrial = false;
        Boolean hasWarehouse = false;
        Boolean hasCommercial = false;
        for(Process__c p : createdProcesses) {
            if(p.Sector__c == 'Industrial') hasIndustrial = true;
            if(p.Type_of_Sector__c == 'Warehouse') hasWarehouse = true;
            if(p.Sector__c == 'Commercial') hasCommercial = true;
        }
        System.assertEquals(true, hasIndustrial, 'Industrial分支未覆盖');
        System.assertEquals(true, hasWarehouse, 'Warehouse分支未覆盖');
        System.assertEquals(true, hasCommercial, 'Commercial分支未覆盖');
    }
}

3. 可选:提升批处理类的可测试性

如果想进一步降低测试依赖,可以给批处理类添加测试专用的构造函数,注入配置参数:

// 测试专用构造函数,跳过组织默认值查询
public ProcessBatch(String testProcessURL, String testKey) {
    ProcessURL = testProcessURL;
    Key = testKey;
}

// 原生产用构造函数保留
public ProcessBatch(){
    SAP_Details__c SAP = SAP_Details__c.getOrgDefaults();
    AccountURL = SAP.Account_API_URL__c;
    ProcessURL = SAP.Process_API_URL__c;
    Key = SAP.API_Key__c;
    System.debug('API:' + ProcessURL + '-' + Key);
}

内容的提问来源于stack exchange,提问作者Gautam T

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 05:35:18