0

I'm working on an apex class that does the following:

  • On a given record, compress field data from it and four child objects into CSV format on a long text field
  • Export the compressed data to files in batches of 1000

The test class I've written (has only one test method) always passes when I run it on its own, but when I've performed a run of multiple test classes it consistently fails due to hitting the CPU time limit. It happens irrespective of whether or not other batch tests are involved.

My understanding is that test methods have independent governor limits, so it perplexes me that other methods running would impact the CPU time. Furthermore, I've nominated my tests to run synchronously I can't see how any other classes would "stall" this one.

The test class is below. The failure happens at the line between startTest() and stopTest().

@isTest
public class ASELCC_Spreadsheet_Generator_test {

public static testMethod void test() {

    List<ASELCC_Participant__c> apList = new List<ASELCC_Participant__c>();
    List<Assessment__c> aList = new List<Assessment__c>();
    List<ASELCC_Subtyping_Questionnaire__c> asqList = new List<ASELCC_Subtyping_Questionnaire__c>();
    List<ASELCC_Parent_Transition_Questionnaire__c> apqList = new List<ASELCC_Parent_Transition_Questionnaire__c>();
    List<ASELCC_Teacher_Transition_Questionnaire__c> atqList = new List<ASELCC_Teacher_Transition_Questionnaire__c>();

    //Populate and insert ASELCC Participants
    for (Integer i=1;i<=200;i++)
    {
        apList.add(new ASELCC_Participant__c(
            Name='TEST',
            Date_of_birth__c = Date.today() - 1));
    }
    insert apList;

    //Populate and insert Time Points, and Transitional Questionnaires
    for(ASELCC_Participant__c ap : apList)
    {
        //Time points
        aList.add(new Assessment__c(Client__c = ap.Id, Time_point__c = 'Entry'));
        /*aList.add(new Assessment__c(Client__c = ap.Id, Time_point__c = 'EOY1'));
        aList.add(new Assessment__c(Client__c = ap.Id, Time_point__c = 'EOY2'));
        aList.add(new Assessment__c(Client__c = ap.Id, Time_point__c = 'Exit'));*/

        //Subtyping Questionnaires
        asqList.add(new ASELCC_Subtyping_Questionnaire__c(
                    ASELCC_Participant__c = ap.Id, Time_Point_PL__c = 'Entry'));

        //Transitional Questionnaires
        apqList.add(new ASELCC_Parent_Transition_Questionnaire__c(
                ASELCC_Participant__c = ap.Id,
                Time_Point__c = 'Kindergarten'));
        //apqList.add(new ASELCC_Parent_Transition_Questionnaire__c(ASELCC_Participant__c = ap.Id,Time_Point__c = 'EOY1'));

        atqList.add(new ASELCC_Teacher_Transition_Questionnaire__c(
                ASELCC_Participant__c = ap.Id,
                Time_Point__c = 'Kindergarten'));
        //atqList.add(new ASELCC_Teacher_Transition_Questionnaire__c(ASELCC_Participant__c = ap.Id,Time_Point__c = 'EOY1'));

    }
    insert aList;
    insert asqList;
    insert apqList;
    insert atqList;


    Test.startTest();

    insert new Research_Spreadsheet_Request__c();

    Test.stopTest();

    delete asqList;
    delete aList;

    delete atqList;
    delete apqList;
    delete apList;

}
}

The batchable class is:

global with sharing class ASELCCSpreadsheetGeneratorBatchable implements Database.Batchable<SObject>, Database.Stateful {

Research_Spreadsheet_Request__c rsr;
final ASELCC_Spreadsheet_Generator asg;
final String query;
Integer fileNumber = 1;

global ASELCCSpreadsheetGeneratorBatchable(Research_Spreadsheet_Request__c rsr){
    this.rsr = rsr;
    asg = new ASELCC_Spreadsheet_Generator(rsr);

    query = 'select Name, Participant_CSV_Data__c, '+
        'T1_Assessment_Data__c, T1_Assessment_Data_subtyping__c,'+                            
        'T2_Assessment_Data__c, T2_Assessment_Data_subtyping__c,'+                             
        'T3_Assessment_Data__c, T3_Assessment_Data_subtyping__c,'+
        'EOY3_Assessment_Data_Summary__c, EOY3_Assessment_Data_subtyping__c,'+
        'T4_Assessment_Data__c, T4_Assessment_Data_subtyping__c,'+
        'Transitional_Data_Parent_KG__c, Transitional_Data_Parent_EOY1__c,'+ 
        'Transitional_Data_Teacher_KG__c, Transitional_Data_Teacher_KG_2__c,'+
        'Transitional_Data_Teacher_EOY1__c, Transitional_Data_Teacher_EOY1_2__c '+ 
        'from ASELCC_Participant__c order by Name asc';
}


global Database.QueryLocator start(Database.BatchableContext BC){
  return Database.getQueryLocator(query);
}

global void execute(Database.BatchableContext BC, List<sObject> scope){
    asg.generate(scope, fileNumber++);
}

global void finish(Database.BatchableContext BC){
    rsr = [select Id from Research_Spreadsheet_Request__c where Id =: rsr.Id];
    rsr.export_status__c = 'Completed';
    update rsr;
}

}

The code for the heavy-lifting is:

public class ASELCC_Spreadsheet_Generator{

Research_Spreadsheet_Request__c rsr;

public ASELCC_Spreadsheet_Generator(Research_Spreadsheet_Request__c rsr)
{
    this.rsr = rsr;
}

public void generate(List<ASELCC_Participant__c> apList, Integer fileNumber){
    String headerRow;
    List<String> rows;
    String rowData;

    //GENERATE CSV FILE

    //Create header row - this will vary depending on the user selection
    headerRow = addHeaderRow();

    //Fetch data, store in list of strings 

    rows = new List<String>();
    for(Integer i=0; i<apList.size(); i++){
        //Fetch the condensed data - this will vary depending on the spreadsheet type
        rowData = writeToRow(apList[i]);
        rows.add(rowData);
    }

    //CREATE FILE
    String finalOutput = headerRow+'\n'+String.join(rows,'\n');

    Blob content = Blob.valueOf(finalOutput);
    finalOutput = '';

    Attachment csv = new Attachment(
        //File name has ASELCC, then the date, then the batch number if more than one file was generated
        Name='ASELCC_'+rsr.Name+'_'+Date.today().format() + 
                ((fileNumber > 1) ? (' - ' + fileNumber.format()) : '') +'.csv',
        ContentType='text/csv',
        Body=content,
        parentId=rsr.Id
        );

    insert csv;

    }

String writeToRow(ASELCC_Participant__c ap){
    //Fetch the condensed data - this will vary depending on the spreadsheet type
    String data = '';

    if (rsr.Participant_Demographic_Details__c) data += ap.Participant_csv_data__c + ',';
    else data += ap.Name + ',';

    if (rsr.T1_Summary_Data__c) data += ap.t1_assessment_Data__c  + ',';
    if (rsr.T1_Raw_Data__c) data += ap.t1_assessment_Data_subtyping__c  + ',';

    if (rsr.T2_Summary_Data__c) data += ap.t2_assessment_Data__c  + ',';
    if (rsr.T2_Raw_Data__c) data += ap.t2_assessment_Data_subtyping__c  + ',';

    if (rsr.T3_Summary_Data__c) data += ap.t3_assessment_Data__c  + ',';
    if (rsr.T3_Raw_Data__c) data += ap.t3_assessment_Data_subtyping__c  + ',';

    if (rsr.EOY3_Summary_Data__c) data += ap.EOY3_assessment_Data_summary__c  + ',';
    if (rsr.EOY3_Subtyping_Data__c) data += ap.EOY3_assessment_Data_subtyping__c  + ',';

    if (rsr.T4_Summary_Data__c) data += ap.t4_assessment_Data__c  + ',';
    if (rsr.T4_Raw_Data__c) data += ap.t4_assessment_Data_subtyping__c  + ',';

    if (rsr.Trans_Questionnaire_Parent_Kinder__c) data += ap.Transitional_Data_Parent_KG__c + ',';
    if (rsr.Trans_Questionnaire_Teacher_Kinder__c ) data += ap.Transitional_Data_Teacher_KG__c + ',';
    if (rsr.Trans_Questionnaire_Teacher_Kinder_2__c ) data += ap.Transitional_Data_Teacher_KG_2__c + ',';

    if (rsr.Trans_Questionnaire_Parent_EOY1__c) data += ap.Transitional_Data_Parent_EOY1__c + ',';
    if (rsr.Trans_Questionnaire_Teacher_EOY1__c) data += ap.Transitional_Data_Teacher_EOY1__c + ',';
    if (rsr.Trans_Questionnaire_Teacher_EOY1_2__c) data += ap.Transitional_Data_Teacher_EOY1_2__c;    

    return data;
}

String addHeaderRow(){
    String header = '';

    if (rsr.Participant_Demographic_Details__c) header += ASELCCPartController.addHeader_participant() + ',';
    else header += 'Child\'s ASELCC Id,';

    if (rsr.T1_Summary_Data__c) header += ASELCCAssessController.addHeaderSection_TnAssessment('Entry') + ',';
    if (rsr.T1_Raw_Data__c) header += ASELCCSubtypingQuestionnaireController.addHeaderSection_SubTypingAssessment('Entry')  + ',';

    if (rsr.T2_Summary_Data__c) header += ASELCCAssessController.addHeaderSection_TnAssessment('EOY1') + ',';
    if (rsr.T2_Raw_Data__c) header += ASELCCSubtypingQuestionnaireController.addHeaderSection_SubTypingAssessment('EOY1') + ',';

    if (rsr.T3_Summary_Data__c) header += ASELCCAssessController.addHeaderSection_TnAssessment('EOY2') + ',';
    if (rsr.T3_Raw_Data__c) header += ASELCCSubtypingQuestionnaireController.addHeaderSection_SubTypingAssessment('EOY2')  + ',';

    if (rsr.eoy3_summary_data__c) header += ASELCCAssessController.addHeaderSection_TnAssessment('EOY3') + ',';
    if (rsr.eoy3_subtyping_data__c) header += ASELCCSubtypingQuestionnaireController.addHeaderSection_SubTypingAssessment('EOY3')  + ',';

    if (rsr.T4_Summary_Data__c) header += ASELCCAssessController.addHeaderSection_TnAssessment('Exit') + ',';
    if (rsr.T4_Raw_Data__c) header += ASELCCSubtypingQuestionnaireController.addHeaderSection_SubTypingAssessment('Exit') + ',';

    if (rsr.Trans_Questionnaire_Parent_Kinder__c) header += ASELCCParentTransController.addHeaderSection('KG') + ',';
    if (rsr.Trans_Questionnaire_Teacher_Kinder__c ) header += ASELCCTeacherTransController.addHeaderSection('KG-1') + ',';
    if (rsr.Trans_Questionnaire_Teacher_Kinder_2__c ) header += ASELCCTeacherTransController.addHeaderSection('KG-2') + ',';

    if (rsr.Trans_Questionnaire_Parent_EOY1__c) header += ASELCCParentTransController.addHeaderSection('EOY1') + ',';
    if (rsr.Trans_Questionnaire_Teacher_EOY1__c) header += ASELCCTeacherTransController.addHeaderSection('EOY1-1') + ','; 
    if (rsr.Trans_Questionnaire_Teacher_EOY1_2__c) header += ASELCCTeacherTransController.addHeaderSection('EOY1-2') + ','; 

    return header;
}

}

Thanks in advance!

  • Please share your code. Does it run with SeeAllData=true? – Adrian Larson Jan 13 '17 at 3:24
  • Code shared. Nah it doesn't – Lachlan B Jan 13 '17 at 3:35
  • 2
    @LachlanB By the way, I noticed that you're "deleting" your test data after you call Test.stopTest(). There's no need to do that unless you're testing a delete trigger, because all changes to the database are rolled back when the unit test ends. – sfdcfox Jan 13 '17 at 3:36
  • 2
    @LachlanB - Have you done any profiling to see where the time is being spent. Could is be a indexing issue, previous tests invalidate the org cache and subsequent tests cause it to have to be rebuilt thus increasing cpu time. There are something that we may not know about the backend. It still exists during the tests. in fact, I have seen test data fail to be inserted due to duplicate value against live data even though seealldata was false and the object was isolated (I don't recall the object) basically profiling your code may help – Eric Jan 13 '17 at 6:07
0

Try to go to Setup --> Apex Test Execution --> Options and check Disable Parallel Apex Testing checkbox.

  • Already had, which is another reason why this is so confusing. Thanks anyway, though! – Lachlan B Jan 13 '17 at 3:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.