Direkt zum Hauptbereich

Batch - Aufbau

Hin und wieder kommt es vor, dass viele Daten im Hintergrund aktualisiert werden müssen. Es ist keine Zauberei, einen Batch dafür zu schreiben. Viel mehr kommt es auf die Technik an. Schließlich muss jeder Batch auch getestet werden. Da Batches asynchron laufen, also zeitversetzt, und nicht unmittelbar nach dem Start, wird die Überprüfung der Testergebnisse nicht einfach sein.
Daher ist es empfehlenswert, die komplette Logik in eine weitere Klasse auszulagern. So können die einzelnen Methoden und Funktionen dieser Klasse sowohl vom Batch benutzt, als auch im Testlauf geprüft werden.

Schematisch dargestellt sieht der Aufbau wie folgt aus:


Batch - Löschen von Daten (f42_Batch_DeleteData)
global class f42_Batch_DeleteData implements Database.Stateful, Database.Batchable<SObject>, Schedulable{
public f42_Batch_DeleteDataHelper batchHelper;
public String soql;

 /* Constructor */
 /*********************/
 public f42_Batch_DeleteData(){
  batchHelper = new f42_Batch_DeleteDataHelper();
 }

 /* execute SchedulableContext */
 /******************************/
 global void execute(SchedulableContext SC) {
  for(String s : batchHelper.buildSearchStrings()){
   f42_Batch_DeleteData b = new f42_Batch_DeleteData();
   b.soql = s;
   Database.executeBatch(b);
  }
 }

 /* start */
 /*********************/
 global Database.QueryLocator start(Database.BatchableContext bc){
  return Database.getQueryLocator(soql);
 }

 /* execute */
 /*********************/
 global void execute(Database.BatchableContext bc, List<sObject> lstObjects){
  batchHelper.Batch_Execute(lstObjects);
 }

 /* finish */
 /*********************/
 global void finish(Database.BatchableContext bc){
  batchHelper.Batch_Finish();
 }
}


Helper (f42_Batch_DeleteDataHelper)
public without sharing class f42_Batch_DeleteDataHelper {
 private list<String> lstSearchObjects;
 public DateTime dtToday;
 /* Constructor */
 /*********************/
 public f42_Batch_DeleteDataHelper(){
  // list with objects to find and delete
  lstSearchObjects = new list<String>();
  lstSearchObjects.add('Object1__c'); // Finde und lösche Object1
  lstSearchObjects.add('Object2__c'); // Finde und lösche Object2 
  dtToday = Date.today();
 }

 /****************************
 Batch_Execute
 */
 public void Batch_Execute(List<sObject> lstObjects){
  if( !(lstObjects == null || lstObjects.isEmpty())){
   //try {
  delete lstObjects;
  //} catch (DmlException e) {
  // Process exception here
  //}
  }
 }

 /****************************
 Batch_Finish
 */
 public void Batch_Finish(){
  // %
 }

 /* BuildSearchString() */
 /********************/
 public list<String> buildSearchStrings(){
  // WHERE part of SOQL
  String wherePart = 'CreatedDate < ' + dtToday.format('yyyy-MM-dd\'T\'HH:mm:ss.000\'Z\'');
  // build search string
  list<String> lstSearch = new list<String>();
  // build soql search string
  for(String s :lstSearchObjects){
   lstSearch.add('SELECT Id FROM ' + s + ' WHERE ' + wherePart);
  }
 return lstSearch;
 }
}



Test (f42_Test_Batch_DeleteData)
@isTest
private class f42_Test_Batch_DeleteData {
 private static Account acc;
 private static list<Object1__c> lstObj1 = new list<Object1__c>();
 private static list<Object2__c> lstObj2 = new list<Object2__c>();
 private static list<f42_Order_Positions__c> lstPositions = new list<f42_Order_Positions__c>();

 /*********************
 test_BatchDeleteData
 just to arrive more test coverage for batch class*/
 static testMethod void test_BatchDeleteData() {
  createTestData();
  Test.startTest();
  f42_Batch_DeleteData newBatch = new f42_Batch_DeleteData();
  newBatch.execute(nulllstObj1);
  Test.stopTest();
 }

 /*********************
 test batch execute method */
 static testMethod void testBatchMethod_Execute() {
  createTestData();
  Test.startTest();
  f42_Batch_DeleteDataHelper batchHelper = new f42_Batch_DeleteDataHelper();
  batchHelper.dtToday = date.today().addDays(1);
  //EXECUTE
  for(String s : batchHelper.buildSearchStrings()){
   list<sObject> lstSobjects = database.query(s);
   batchHelper.Batch_Execute(lstSobjects);
  }
  //check
  list<Object1__c> lst1 = [select id from Object1__c];
  list<Object2__c> lst2 = [select id from Object2__c];
  
  system.assert(lst1.isEmpty());
  system.assert(lst2.isEmpty());
  Test.stopTest();
 }

 /*********************
 test batch finish method */
 static testMethod void testBatchMethod_Finish() {
  Test.startTest();
  f42_Batch_DeleteDataHelper batchHelper = new f42_Batch_DeleteDataHelper();
  //FINISH
  batchHelper.Batch_Finish();
  Test.stopTest();
 }

 /*********************
 test for schedule batch */
 static testMethod void testScheduleForBatch(){
  Test.startTest();

  Date dtToday = Date.today();
  DateTime dtNextRun = DateTime.newInstance(dtToday.year(), dtToday.month(), dtToday.day(), 1, 0, 0);
  dtNextRun = dtNextRun.addDays(1);

  String ss = String.valueOf(dtNextRun.second());
  String mm = String.valueOf(dtNextRun.minute());
  String hh = String.valueOf(dtNextRun.hour());
  String d = String.valueOf(dtNextRun.day());
  String m = String.valueOf(dtNextRun.month());
  String y = String.valueOf(dtNextRun.year());

  String jobName = 'TEST Batch: ' + dtNextRun.formatLong();
  String ScheduleParam = '00' + ' ' + mm + ' ' + hh + ' ' + d + ' ' + m + ' ? ' + y;

  //Start Scheduled Job
  String jobId = System.schedule(jobName, ScheduleParam, new f42_Batch_DeleteData());

  //Get the information from the CronTrigger API object
  CronTrigger ct = [SELECT Id, CronExpression, TimesTriggered, NextFireTime FROM CronTrigger WHERE id = :jobId];

  // Verify the expressions are the same
  System.assertEquals(ScheduleParam, ct.CronExpression);

  // Verify the job has not run
  System.assertEquals(0, ct.TimesTriggered);

  Test.stopTest();
 }

 /******************
 Test Data
 **/
 private static void createTestData(){
  Integer maxNumber = 10;
  // ACCOUNT
  acc = f42_TestDataGenerator.createTestAccount(1, true);
  system.assertnotequals(null, acc.id);
  // OBJECT 1
  for(Integer i = 1; i<=maxNumber; i++){
   lstObj1.add(f42_TestDataGenerator.createTestObject1(i, acc.Id, false));
  }
  insert lstObj1;
  system.assertEquals(maxNumber, lstObj1.size());
  // OBJECT 2
  for(Integer i = 1; i<=maxNumber; i++){
   lstObj2.add(f42_TestDataGenerator.createTestObject2(i, acc.Id, false));
  }
  insert lstObj2;
  system.assertEquals(maxNumber, lstObj2.size());
 }
}


Testdata Generator (f42_TestDataGenerator)
public without sharing class f42_TestDataGenerator {
 //Account
 public static Account createTestAccount(Double i, Boolean insertObject){
  Account a = new Account();
  a.Name = 'Test ' + i;
  a.BillingStreet = 'Musterstr.';
  a.BillingPostalCode = '81671';
  a.BillingCity = 'München';
  a.BillingCountry = 'Germany';

  if(insertObject)
   insert a;

  return a;

 }

//Object1
 public static Object1__c createTestObject1(Double i, Boolean insertObject){
  Object1__c obj = new Object1__c();
  obj.Name = 'Test ' + i;

  if(insertObject)
   insert obj;

  return obj;

 }

//Object2
 public static Object2__c createTestObject2(Double i, Boolean insertObject){
  Object2__c obj = new Object2__c();
  obj.Name = 'Test ' + i;

  if(insertObject)
   insert obj;

  return obj;

 }
}

Kommentare

Beliebte Posts aus diesem Blog

Salesforce - Datensätze sperren (Umsetzung)

Record Lock - nun, die Umsetzung.Wir haben bereits mehrere VisualForce Pages im Einsatz. Aus dem Grund ist das zuvor beschriebene Konzept darauf zugeschnitten. Selbstverständlich lässt sich der Code auch für Standard und Custom Objects verwenden, ohne den Einsatz von VisualForce Pages. Na ja, mindestens eine VF Page brauchen wir eigentlich schon - diejenige, die von dem dem "Edit" Button aufgerufen wird.

Ich zeige jetzt die wichtigsten Bausteine aus der Umsetzung.
1) Diese VF Page wird aufgerufen sobald man den "Edit" Button betätigt

<apex:pagestandardController="myObject"title="{!myObject.Name}"extensions="LockController"action="{!SwitchToPage}">
<!-- READ MODE --> <apex:includepageName="myObjectRead"rendered="{!isEditMode == false}"/>
<!-- EDIT MODE --> <apex:includepageName="myObjectEdit"rendered="{!isEditMode == true}"/>
</apex:page>

Folgendes passiert …

Salesforce.com Certified Advanced Administrator (WI15)

Im Vergleich zu den beiden Consultants Zertifizierungen war die Advanced Admin ein Spaziergang!
Allerdings ganz ohne Vorbereitung schafft man die Prüfung auch nicht.


Hier sind einige Fragen/Antworten aus der aktuellen Prüfung.

1. Capabilities of collaborative forecasting?
- Rename categories
- Forecast using opportunity splits
- Overlay Quota
- Add categories
- Select a default forecast currency setting

2. We have four Sales Regions. Each of the region's VPs wants to have a dashboard emailed every Monday.
- Create a separate dashboard for each VP
- Create one dashboard using Visualforce
- Create one dashboard that includes a filter for each region
- Create a reporting snapshot

3. User is trying to access content in a library, but receiving an insufficent privileges message.
How to troubleshoot?
- has the user's profile the CRM content permission enabled
- user has been granted permission "Viewer" to the library
- user's record has the CRM content permisson enabled
- …

Emails mit APEX | SINGLE_EMAIL_LIMIT_EXCEEDED

Email-Versand mit Visualforce und Apex kann schon manchmal Kopfschmerzen bereiten....

Entweder ist die Email zu groß, oder zu viele Attachments oder der HeapSize "beschwert sich" oder, wenn letztendlich alles läuft,  man wird von den Salesforce Limits zurück zum Ausgangspunkt katapultiert.

Das kann doch nicht so schwer sein!
Ist es auch nicht, wenn man das schon einmal gemacht hat ;-)

Habe eine Visualforce Maske zum Versenden von HTML Emails programmiert.
Diese werden als SingleEmailMessage gesendet:
Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
Sobald die Anzahl der pro Tag gesendeten Emails den Salesforce Limit "knackt", wird dem User die entsprechende Meldung eingeblendet.



Die auf die Org bezogene Limitierung lässt sich mit LIMITS.getLimitEmailInvocations() berechnen.

Hier ein Auszug aus den "Execution Governors and Limits"
https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_gov_limits.htm

A Dev Edition org ha…