[SalesForce] Apex CPU time limit exceeded (How to test trigger using large number of records?)

I wrote a trigger and need to use test the trigger by insert/update/delete large number

I wrote one method to test the insert/update/delete trigger and I ran into Apex CPU Limit.

My requirement is to test 1000 classes and each class has at least 600 student.

Here's a schema:

Contact-Student Master-Detail on Student

Class-Student Lookup on Student

The trigger is to get the number of different types of students under a class and set the value on the class field.

@isTest
public class Test_StudentTrigger {        
//static      
@isTest
public static void testInsert()
{   
    Test.startTest();
    List<Class__c> classes=new List<Class__c>();
    For(Integer i=0;i<1200;i++){
        Class__c c=new Class__c(name='c'+i);
        classes.add(c);
    }
    Insert classes;
    classes=[select id from class__c];
    System.debug('class num:'+classes.size());
    List<Student__c> students=new List<Student__c>();
    List<Contact> contacts=new List<Contact>();
    for(integer i=0;i<10000;i++){
        Contact c=new Contact(firstname='f'+i,lastname='l'+i);
        contacts.add(c);
    }
    Insert contacts;
    contacts=[select id from contact];
    System.debug('contacts num:'+contacts.size());

    For(Class__c c: classes)
    {
        Integer count=0;
        For(Integer i=0;i<250;i++)
        {               
            Student__c s=new Student__c(Contact__c=contacts[count++].Id,Class__c=c.Id,Status__c='Interested');
            students.add(s);
        }
        For(Integer i=0;i<150;i++)
        {
            Student__c s=new Student__c(Contact__c=contacts[count++].Id,Class__c=c.Id,Status__c='Verbally Committed');
            students.add(s);
        }
        For(Integer i=0;i<400;i++)
        {
            Student__c s=new Student__c(Contact__c=contacts[count++].Id,Class__c=c.Id,Status__c='Registered and Paid');
            students.add(s);
        }
    }
    insert Students;
    System.debug('line 43 num student: '+students.size());
    classes=[select id,Interested_Students__c,Verbally_Committed_Students__c,Registered_Students__c,Number_of_Students__c from class__c];
    System.debug('Line 46 num classes: '+classes.size());
    For(Class__c c: classes)
    {
        System.assertEquals(250, c.Interested_Students__c);
        System.assertEquals(150, c.Verbally_Committed_Students__c);
        System.assertEquals(400, c.Registered_Students__c);
        System.assertEquals(800, c.Number_of_Students__c);
    }

    System.debug('end inset test');

    //test update trigger
    //
    //
    students=[select id,Status__c,Class__c from Student__c];
    System.debug('line 61 num student: '+students.size());
    For(Student__c s : students)
    {
        if(s.Status__c=='Interested')
        {
            s.Status__c='Registered and Paid';
        }            
    }

    update students;
    system.debug(students);
    classes=[select id,Interested_Students__c,Verbally_Committed_Students__c,Registered_Students__c,Number_of_Students__c from class__c];
    For(Class__c c: classes)
    {
        System.assertEquals(0, c.Interested_Students__c);
        System.assertEquals(150, c.Verbally_Committed_Students__c);
        System.assertEquals(650, c.Registered_Students__c);
        System.assertEquals(800, c.Number_of_Students__c);
    }
    students=[select id,Status__c,Class__c from Student__c];
    For(Student__c s : students)
    {
        if(s.Status__c=='Verbally Committed')
        {
            s.Status__c='Interested';
        }            
    }
    update students;
    classes=[select id,Interested_Students__c,Verbally_Committed_Students__c,Registered_Students__c,Number_of_Students__c from class__c];
    For(Class__c c: classes)
    {
        System.assertEquals(150, c.Interested_Students__c);
        System.assertEquals(0, c.Verbally_Committed_Students__c);
        System.assertEquals(650, c.Registered_Students__c);
        System.assertEquals(800, c.Number_of_Students__c);
    }
    List<Student__c> studentsToDelete=new List<Student__c>();
    students=[select id,Status__c,Class__c from Student__c];
    For(Student__c s : students)
    {
        if(s.Status__c=='Interested')
        {
            studentsToDelete.add(s);
        }            
    }
    delete studentsToDelete;
    studentsToDelete.clear();
    classes=[select id,Interested_Students__c,Verbally_Committed_Students__c,Registered_Students__c,Number_of_Students__c from class__c];
    For(Class__c c: classes)
    {
        System.assertEquals(0, c.Interested_Students__c);
        System.assertEquals(0, c.Verbally_Committed_Students__c);
        System.assertEquals(650, c.Registered_Students__c);
        System.assertEquals(650, c.Number_of_Students__c);
    }
    Test.stopTest();
}

}

public class StudentTriggerHandler{

//integer Interested=0;integer Registered=0;integer Verbally_Committed=0;Integer totalStudent=0;
//key:ClassID plus Status, value:Number of student
public static Map<String,Integer> interestedNumMaps=new Map<String,Integer>();
public static Map<String,Integer> RegisteredNumMaps=new Map<String,Integer>();
public static Map<String,Integer> CommittedNumMaps=new Map<String,Integer>();
//store all classes involved
public static Set<String> classIDs=new Set<String>();
public static void handleBeforeInsert(List<Student__c> students)
{
        For(Student__c s : students)
        {
            String key=s.Class__c;
            classIDs.add(s.Class__c);
            Integer CurrentValue=1;
            if(s.Status__c=='Registered and Paid')
            {
                if(RegisteredNumMaps.containsKey(key)){
                    CurrentValue=RegisteredNumMaps.get(key);
                    RegisteredNumMaps.put(key,CurrentValue+1);
                }
                else{
                    RegisteredNumMaps.put(key,CurrentValue);
                }
            }
            else if(s.Status__c=='Verbally Committed')
            {
                if(CommittedNumMaps.containsKey(key)){
                    CurrentValue=CommittedNumMaps.get(key);
                    CommittedNumMaps.put(key,CurrentValue+1);
                }
                else{
                    CommittedNumMaps.put(key,CurrentValue);
                }
            }
            else if(s.Status__c=='Interested')
            {
                if(interestedNumMaps.containsKey(key)){
                    CurrentValue=interestedNumMaps.get(key);
                    interestedNumMaps.put(key,CurrentValue+1);
                }
                else{
                    interestedNumMaps.put(key,CurrentValue);
                }
            }   
        }
    System.debug(CommittedNumMaps);
        List<Class__c> classes=[select Id,Number_of_Students__c,Interested_Students__c,Registered_Students__c,Verbally_Committed_Students__c
                           From Class__c where id in : classIDs];
        For(Class__c c: classes)
        {
            if(RegisteredNumMaps.containsKey(c.Id))
            {
                if(c.Registered_Students__c!=null)
                {
                    c.Registered_Students__c+=RegisteredNumMaps.get(c.Id);
                }
                else
                {
                    c.Registered_Students__c=RegisteredNumMaps.get(c.Id);
                }
                if(c.Number_of_Students__c!=null){
                    c.Number_of_Students__c+=RegisteredNumMaps.get(c.Id);}
                else
                {
                    c.Number_of_Students__c=RegisteredNumMaps.get(c.Id);
                }
            }
            if(interestedNumMaps.containsKey(c.Id))
            {
                if(c.Interested_Students__c!=null){
                    c.Interested_Students__c+=interestedNumMaps.get(c.Id);}
                else
                {
                    c.Interested_Students__c=interestedNumMaps.get(c.Id);                        
                }
                if(c.Number_of_Students__c!=null){
                 c.Number_of_Students__c+=interestedNumMaps.get(c.Id);
                }
                else
                {c.Number_of_Students__c=interestedNumMaps.get(c.Id);

                }
            }
            if(CommittedNumMaps.containsKey(c.Id))
            {
                if(c.Verbally_Committed_Students__c!=null){
                    c.Verbally_Committed_Students__c+=CommittedNumMaps.get(c.Id);
                }
                else
                {
                    c.Verbally_Committed_Students__c=CommittedNumMaps.get(c.Id);
                }
                if(c.Number_of_Students__c!=null){
                    c.Number_of_Students__c+=CommittedNumMaps.get(c.Id);
                }
                else
                {
                    c.Number_of_Students__c=CommittedNumMaps.get(c.Id);
                }
            }
        }
        update classes;

}
public static void handleBeforeUpdate(List<Student__c> newStudents,List<Student__c> OldStudents)
{
    Map<Id,String> newMap=new Map<Id,String>();
    Map<Id,String> oldMap=new Map<Id,String>();
    For(Student__c s:oldStudents)
    {
        oldMap.put(s.Id, s.Status__c);
    }
    For(Student__c s : newStudents)
    {
        String oldStatus=oldMap.get(s.Id);
        if(oldStatus!=s.Status__c)
        {
            String key=s.Class__c;
            classIDs.add(s.Class__c);  
            Integer CurrentValue=1;
            if(s.Status__c=='Registered and Paid')
            {
                if(RegisteredNumMaps.containsKey(key)){
                    CurrentValue=RegisteredNumMaps.get(key);
                    RegisteredNumMaps.put(key,CurrentValue+1);
                }
                else{
                    RegisteredNumMaps.put(key,CurrentValue);
                }

                if(oldStatus=='Verbally Committed')
                {
                    if(CommittedNumMaps.containsKey(key)){
                    CurrentValue=CommittedNumMaps.get(key);
                    CommittedNumMaps.put(key,CurrentValue-1);
                    }
                    else{
                        RegisteredNumMaps.put(key,-1);
                    }
                }
                else
                {
                    if(interestedNumMaps.containsKey(key)){
                    CurrentValue=interestedNumMaps.get(key);
                    interestedNumMaps.put(key,CurrentValue-1);
                    }
                    else{
                        interestedNumMaps.put(key,-1);
                    }
                }
            }
            else if(s.Status__c=='Verbally Committed')
            {
                if(CommittedNumMaps.containsKey(key)){
                    CurrentValue=CommittedNumMaps.get(key);
                    CommittedNumMaps.put(key,CurrentValue+1);
                }
                else{
                    CommittedNumMaps.put(key,CurrentValue);
                }
                if(oldStatus=='Interested')
                {
                    if(interestedNumMaps.containsKey(key)){
                    CurrentValue=interestedNumMaps.get(key);
                    interestedNumMaps.put(key,CurrentValue-1);
                    }
                    else{
                        interestedNumMaps.put(key,-1);
                    }
                }
                else
                {
                    if(RegisteredNumMaps.containsKey(key)){
                    CurrentValue=RegisteredNumMaps.get(key);
                    RegisteredNumMaps.put(key,CurrentValue-1);
                    }
                    else{
                        RegisteredNumMaps.put(key,-1);
                    }
                }
            }
            else if(s.Status__c=='Interested')
            {
                if(interestedNumMaps.containsKey(key)){
                    CurrentValue=interestedNumMaps.get(key);
                    interestedNumMaps.put(key,CurrentValue+1);
                }
                else{
                    interestedNumMaps.put(key,CurrentValue);
                }
                if(oldStatus=='Verbally Committed')
                {
                    if(CommittedNumMaps.containsKey(key)){
                    CurrentValue=CommittedNumMaps.get(key);
                    CommittedNumMaps.put(key,CurrentValue-1);
                    }
                    else{
                        CommittedNumMaps.put(key,-1);
                    }
                }
                else
                {
                    if(RegisteredNumMaps.containsKey(key)){
                    CurrentValue=RegisteredNumMaps.get(key);
                    RegisteredNumMaps.put(key,CurrentValue-1);
                    }
                    else{
                        RegisteredNumMaps.put(key,-1);
                    }
                }
            }   
        }           





        }
        List<Class__c> classes=[select Id,Number_of_Students__c,Interested_Students__c,Registered_Students__c,Verbally_Committed_Students__c
                           From Class__c where id in : classIDs];
        For(Class__c c: classes)
        {
            if(RegisteredNumMaps.containsKey(c.Id))
            {
                if(c.Registered_Students__c!=null){
                    c.Registered_Students__c+=RegisteredNumMaps.get(c.Id);}
                else
                {
                    c.Registered_Students__c=RegisteredNumMaps.get(c.Id);
                }
                //c.Number_of_Students__c+=RegisteredNumMaps.get(c.Id);
            }
            if(interestedNumMaps.containsKey(c.Id))
            {
                if(c.Interested_Students__c!=null){
                    c.Interested_Students__c+=interestedNumMaps.get(c.Id);}
                else
                {
                    c.Interested_Students__c=interestedNumMaps.get(c.Id);
                }
                 //c.Number_of_Students__c+=interestedNumMaps.get(c.Id);
            }
            if(CommittedNumMaps.containsKey(c.Id))
            {
                if(c.Verbally_Committed_Students__c!=null){
                    c.Verbally_Committed_Students__c+=CommittedNumMaps.get(c.Id);}
                else
                {c.Verbally_Committed_Students__c=CommittedNumMaps.get(c.Id);
                }
                // c.Number_of_Students__c+=CommittedNumMaps.get(c.Id);
            }
        }
        update classes;
}
public static void handleBeforeDelete(List<Student__c> students)
{
        For(Student__c s : students)
        {
            String key=s.Class__c;
            classIDs.add(s.Class__c);
            Integer CurrentValue=1;
            if(s.Status__c=='Registered and Paid')
            {
                if(RegisteredNumMaps.containsKey(key)){
                    CurrentValue=RegisteredNumMaps.get(key);
                    RegisteredNumMaps.put(key,CurrentValue+1);
                }
                else{
                    RegisteredNumMaps.put(key,CurrentValue);
                }
            }
            else if(s.Status__c=='Verbally Committed')
            {
                if(CommittedNumMaps.containsKey(key)){
                    CurrentValue=CommittedNumMaps.get(key);
                    CommittedNumMaps.put(key,CurrentValue+1);
                }
                else{
                    CommittedNumMaps.put(key,CurrentValue);
                }
            }
            else if(s.Status__c=='Interested')
            {
                if(interestedNumMaps.containsKey(key)){
                    CurrentValue=interestedNumMaps.get(key);
                    interestedNumMaps.put(key,CurrentValue+1);
                }
                else{
                    interestedNumMaps.put(key,CurrentValue);
                }
            }   
        }
        List<Class__c> classes=[select Id,Number_of_Students__c,Interested_Students__c,Registered_Students__c,Verbally_Committed_Students__c
                           From Class__c where id in : classIDs];
        For(Class__c c: classes)
        {
            if(RegisteredNumMaps.containsKey(c.Id))
            {
                if(c.Registered_Students__c!=null){
                    c.Registered_Students__c-=RegisteredNumMaps.get(c.Id);
                }
                else
                {
                    c.Registered_Students__c=0-RegisteredNumMaps.get(c.Id);
                }
                if(c.Number_of_Students__c!=null){
                    c.Number_of_Students__c-=RegisteredNumMaps.get(c.Id);
                }
                else
                {
                    c.Number_of_Students__c=0-RegisteredNumMaps.get(c.Id);
                }

            }
            if(interestedNumMaps.containsKey(c.Id))
            {
                if(c.Interested_Students__c!=null){
                    c.Interested_Students__c-=interestedNumMaps.get(c.Id);
                }
                else
                {
                    c.Interested_Students__c=0-interestedNumMaps.get(c.Id);
                }
                if(c.Number_of_Students__c!=null)
                {
                    c.Number_of_Students__c-=interestedNumMaps.get(c.Id);
                }
                else
                {
                    c.Number_of_Students__c=0-interestedNumMaps.get(c.Id);
                }

            }
            if(CommittedNumMaps.containsKey(c.Id))
            {
                if(c.Verbally_Committed_Students__c!=null){
                    c.Verbally_Committed_Students__c-=CommittedNumMaps.get(c.Id);
                }
                else
                {
                    c.Verbally_Committed_Students__c=0-CommittedNumMaps.get(c.Id);
                }
                if(c.Number_of_Students__c!=null)
                {
                    c.Number_of_Students__c-=CommittedNumMaps.get(c.Id);
                }
                else
                {
                    c.Number_of_Students__c=0-CommittedNumMaps.get(c.Id);
                }

            }
        }
        update classes;

}

}

Modified Initiate Method:  Still ran into Apex CPU limit when running for loop
static List<Student__c> students=new List<Student__c>();
@testSetup
public static void setup() {
    List<Class__c> classes=Test.loadData(Class__c.sObjectType,'test_classes');        
    System.debug('class num:'+classes.size());
    List<Contact> contacts=Test.loadData(Contact.sObjectType,'test_contacts');
    System.debug('contacts num:'+contacts.size());       
    For(Class__c c: classes)
    {
        Integer count=0;
        For(Integer i=0;i<600;i++){               
            Student__c s=new Student__c(Contact__c=contacts[count++].Id,Class__c=c.Id,Status__c='Interested');
            students.add(s);
        }
    }

}

Best Answer

Rather than one large test method have you tried separating out each of your scenarios (i.e. each insert, update, delete) into separate test methods? You get a new set of limits for each method.

Further to this you should try to use Test.startTest() and Test.stopTest() closer to the actual methods being tested (i.e. the inserts, updates and deletes), as these will reset your limits before those operations run meaning that all of your setup code's CPU time will be excluded (unless it goes over on it's own).

You can also move all of your setup code out of your test methods using the new @testSetup annotation.

Here is an example using your code (PS. I have no idea if your asserts will still pass because I don't know how dependent they are on the previous steps, this could also be tidied up more but I'll leave that up to you).

@isTest
public class Test_StudentTrigger {   
    @testSetup
    public static void setup() {
        List<Class__c> classes=new List<Class__c>();
        For(Integer i=0;i<1200;i++){
            Class__c c=new Class__c(name='c'+i);
            classes.add(c);
        }
        Insert classes;
        classes=[select id from class__c];
        System.debug('class num:'+classes.size());
        List<Contact> contacts=new List<Contact>();
        for(integer i=0;i<10000;i++){
            Contact c=new Contact(firstname='f'+i,lastname='l'+i);
            contacts.add(c);
        }
        Insert contacts;
        contacts=[select id from contact];
        System.debug('contacts num:'+contacts.size());
    }

    //static      
    @isTest
    public static void testInsert()
    {           
        List<Contact> contacts=[select id from contact];
        List<Class__c> classes=[select id from class__c];
        List<Student__c> students=new List<Student__c>();

        For(Class__c c: classes)
        {
            Integer count=0;
            For(Integer i=0;i<250;i++)
            {               
                Student__c s=new Student__c(Contact__c=contacts[count++].Id,Class__c=c.Id,Status__c='Interested');
                students.add(s);
            }
            For(Integer i=0;i<150;i++)
            {
                Student__c s=new Student__c(Contact__c=contacts[count++].Id,Class__c=c.Id,Status__c='Verbally Committed');
                students.add(s);
            }
            For(Integer i=0;i<400;i++)
            {
                Student__c s=new Student__c(Contact__c=contacts[count++].Id,Class__c=c.Id,Status__c='Registered and Paid');
                students.add(s);
            }
        }

        Test.startTest();
        insert Students;
        Test.stopTest();

        System.debug('line 43 num student: '+students.size());
        classes=[select id,Interested_Students__c,Verbally_Committed_Students__c,Registered_Students__c,Number_of_Students__c from class__c];
        System.debug('Line 46 num classes: '+classes.size());
        For(Class__c c: classes)
        {
            System.assertEquals(250, c.Interested_Students__c);
            System.assertEquals(150, c.Verbally_Committed_Students__c);
            System.assertEquals(400, c.Registered_Students__c);
            System.assertEquals(800, c.Number_of_Students__c);
        }

        System.debug('end inset test');
    }

    @isTest
    public static void testUpdate())
    {
        List<Contact> contacts=[select id from contact];
        List<Class__c> classes=[select id from class__c];
        List<Student__c> students=new List<Student__c>();

        For(Class__c c: classes)
        {
            Integer count=0;
            For(Integer i=0;i<250;i++)
            {               
                Student__c s=new Student__c(Contact__c=contacts[count++].Id,Class__c=c.Id,Status__c='Interested');
                students.add(s);
            }
            For(Integer i=0;i<150;i++)
            {
                Student__c s=new Student__c(Contact__c=contacts[count++].Id,Class__c=c.Id,Status__c='Verbally Committed');
                students.add(s);
            }
            For(Integer i=0;i<400;i++)
            {
                Student__c s=new Student__c(Contact__c=contacts[count++].Id,Class__c=c.Id,Status__c='Registered and Paid');
                students.add(s);
            }
        }

        insert Students;

        //test update trigger
        //
        //
        students=[select id,Status__c,Class__c from Student__c];
        System.debug('line 61 num student: '+students.size());
        For(Student__c s : students)
        {
            if(s.Status__c=='Interested')
            {
                s.Status__c='Registered and Paid';
            }            
        }

        Test.startTest();
        update students;
        Test.stopTest();

        system.debug(students);
        classes=[select id,Interested_Students__c,Verbally_Committed_Students__c,Registered_Students__c,Number_of_Students__c from class__c];
        For(Class__c c: classes)
        {
            System.assertEquals(0, c.Interested_Students__c);
            System.assertEquals(150, c.Verbally_Committed_Students__c);
            System.assertEquals(650, c.Registered_Students__c);
            System.assertEquals(800, c.Number_of_Students__c);
        }
    }
}