Clean the mess!
For start let's define some "more-or-less" real life scenario for those, who want to follow in their orgs. We have a custom object Log (API Name: Log__c, Name Type: Auto Number), that we use to log exceptions, integrations and some other events worth saving. Custom object has several fields containing information like error message and stack trace, but it is not really important for our purpose. Unfortunately our predecessors (or anyone else we can easily blame) didn’t do their work properly creating immense number of bugs in the system and our object is overloaded with logged exceptions and takes too much of the storage.
Fixing all the bugs in the system will take months, so we need to take care of the storage first. Our 2 000 hard-working Oompa Loompas… 2 000 hard working users are daily generating more records, than we can delete in one transaction. We also need to store logs for at least 7 days. Let’s quickly write some Batch Apex, that will be run every night out of working hours.
The code is pretty clear, so I will not spend any more time commenting it.
global class
DeleteLogsimplements
Schedulable, Database.Batchable<SObject> {private
DateTime DELETION_DEADLINE;global
Database.QueryLocator start (Database.BatchableContext BC) { DELETION_DEADLINE = DateTime.now().addDays(-6);return
Database.getQueryLocator('SELECT Id FROM Log__c WHERE CreatedDate < :DELETION_DEADLINE'
); }global void
execute(Database.BatchableContext BC,List
<Log__c> scope) {delete
scope; }global void
finish(Database.BatchableContext BC) { }global void
execute(SchedulableContext SC) { Database.executeBatch(new DeleteLogs()); } }
Alright, although the class is a bit ugly, it should work. We are going to celebrate it with 100 % code coverage. First we will try to do it in one method. We will schedule our batch inbetween Test.startTest() and Test.stopTest().
@IsTestprivate class
DeleteLogsTest {private static final
Integer TOTAL_RECORDS = 3;private static final
Integer RECORDS_TO_DELETE = 2;private static final
Integer REMAINING_RECORDS = TOTAL_RECORDS - RECORDS_TO_DELETE; // Let's create Log records, but only some of them older than 7 days @testSetupstatic void
setup() { // Create logsList
<Log__c> logs = newList
<Log__c>();for
(Integer i = 0; i < TOTAL_RECORDS; i++) { logs.add(new Log__c()); }insert
logs; // Set createdDate 7 days back for some of the recordsfor
(Integer i = 0; i < RECORDS_TO_DELETE; i++) { // "Midnights tests" won't cause any issue here, because we are not seeking // records exactly 7 days old, but at least 7 days oldTest
.setCreatedDate(logs[i].Id, DateTime.now().addDays(-7)); } } // Let's test both Batchable and Schedulable at once @IsTestprivate static void
testWillNotWork() {Test
.StartTest();System
.schedule('Log Deletion'
,'0 0 1 * * ?'
, new DeleteLogs());Test
.StopTest();List
<Log__c> remainingLogs = [SELECT
IdFROM
Log__c];System
.assertEquals(REMAINING_RECORDS, remainingLogs.size
(),'Incorrect number of records was deleted.'
); } }
Proper test
Assertation failed, because no record was deleted. But why, when documentation says "All asynchronous calls made after the startTest method are collected by the system. When stopTest is executed, all asynchronous processes are run synchronously."? Because system doesn't collect asynchronous processes nested in other asynchronous processes. It works on one level only.
So, how can we achieve our goal of 100 % code coverage? We need to test Batchable and Schedulable part separately. In testBatchable method we are going to test deletion of records and in testSchedulable we are only testing, if Schedulable part executes Batchable.
@IsTestprivate class
DeleteLogsTest {private static final
Integer TOTAL_RECORDS = 3;private static final
Integer RECORDS_TO_DELETE = 2;private static final
Integer REMAINING_RECORDS = TOTAL_RECORDS - RECORDS_TO_DELETE; // Let's create Log records, but only some of them older than 7 days @testSetupprivate static void
setup() { // Create logsList
<Log__c> logs = newList
<Log__c>();for
(Integer i = 0; i < TOTAL_RECORDS; i++) { logs.add(new Log__c()); }insert
logs; // Set createdDate 7 days back for some of the recordsfor
(Integer i = 0; i < RECORDS_TO_DELETE; i++) { // "Midnights tests" won't cause any issue here, because we are not seeking // records exactly 7 days old, but at least 7 days oldTest
.setCreatedDate(logs[i].Id, DateTime.now().addDays(-7)); } } @IsTeststatic void
testBatchable() {Test
.StartTest(); Database.executeBatch(new DeleteLogs());Test
.StopTest();List
<Log__c> remainingLogs = [SELECT
IdFROM
Log__c];System
.assertEquals(REMAINING_RECORDS, remainingLogs.size
(),'Incorrect number of records was deleted.'
); } @IsTestprivate static void
testSchedulable() {Test
.startTest();System
.schedule('Delete Logs Schedulable'
,'0 0 1 * * ?'
, new DeleteLogs());Test
.stopTest();List
<AsyncApexJob> batchJobs = [SELECT
Id, StatusFROM
AsyncApexJobWHERE
ApexClass.Name ='DeleteLogs'
AND
JobType ='BatchApex'
];System
.assertEquals(1, batchJobs.size
(),'Unexpected number of batch jobs ran: '
+ batchJobs);System
.assertEquals('Queued'
, batchJobs[0].Status,'Job planned with an unexpected status.'
); } }
Run Tests… 1, 2… Yes! We did it again!
awesome article,the content has very informative ideas, waiting for the next update…
ReplyDeleteCCNP course in chennai
CCNP training in chennai
raft titanium | TITanium Arts
ReplyDeleteOur titanium flask raft titanium is made of two materials - platinum-iron, nickel and platinum-carbon. 2021 ford escape titanium hybrid The materials titanium bike frame are apple watch titanium vs aluminum graphite, graphite and zinc ecosport titanium oxide.
This comment has been removed by the author.
ReplyDelete