How to be more productive with Salesforce Tests

A new Salesforce test utility developed during one of our projects helps developers to be more productive when working with unit tests through automated test data creation and simplified test data management.

Experienced software developers always tend to invest a significant amount of resources into good tests, which will ensure a product’s stability and save resources in the long run. Additionally, in Salesforce test coverage is required to be more than 75%.

 

Records are inserted into the ‘test scope sandbox’ and all of the changes are rolled back after the test has finished running. Some of the Objects’ records from organisation are visible in the test scope by default, like User, Profile, UserRole, RecordType. Since the Spring 2015 release, Saleforce supports one @testSetup annotated method per test class, which creates test data available for all test methods in a given class and remains the same at the beginning of each test method run.

A test method will modify this data and validate results with assets. A test method that will run after the previous one will not be affected by changes performed in the previous test method. Sharing the state between test methods in static variables is not supported. Also, SOQL statements limits are not automatically reset after the @testSetup method finished running. The Test.startTest(); method call will reset the limits and it is reasonable to put it at the beginning of each test method.

 

New Approaches

Before creating test data, it is helpful to be able to deactivate triggers. This can be achieved by using a standartised trigger pattern, an example of which can be found here. Deactivation will allow to preserve the expected test data state, save processing time and keep the system within the limits. A common problem occuring without the deactivation of triggers is hitting the 100 SQL statements limit.

A new test utility developed during one of our projects is a two steps process. The new concept about it: automated test data creation and simplified test data management with a goal of making test data as close to a real environment as possible to improve the quality of tests and to help developers to be more productive when working with unit tests.

 

First step. Preparing test data.

There was a script developed that extracts real organisation data which has been prepared by administrators using standard or Visualforce pages. This data is then stored as a static resource file with a customised version of CSV format, see Picture 1.

salesforce tests-1
Picture 1. Example of a customised version of CSV format used for storing test data of multiple SObjects.

 

The extraction script accepts a map as a parameter. A key of the map is the SObject name, a value is the SOQL filter criteria. An Example is shown in Picture 2.

salesforce tests-2
Picture 2. Example of filter criteria for crawler within simple user interface.

 

Clicking ‘Submit filter’ button will start the crawling process. Recursive calls gather Ids of parent records and go up in the objects hierarchy until reaching an object without further references. For some of our tests, the hierarchy reaches up to 40 objects in size. Queries are automatically generated with all of the fields from SObjects retrieved by Schema describe calls. There is an option to configure the skipping of certain fields, non insertable fields like formula fields are skipped by default. Commonly used references like User or RecordType are excluded from crawling to keep the process within the limits. Generally, exploring these references is not necessary. References between SObjects are restored using mapping between Ids mentioned in the CSV file and Ids created after inserting records.

After the processing is complete the server returns a string that is fed into ace editor, an open source JavaScript text editor. There is also a possibility to save the file as a static resource with a help of jsForce library, see Picture 3.

salesforce tests-3
Picture 3. User interface for reviewing, modifying, saving results of SObjects records retrival.

 

One of the extra features of the process is the ability to replace Ids with some other meaningful strings. Picture 1 shows ‘{{!Project Member}}’ and ‘{{!System Administrator}}’ as examples. These references are profile names and will be replaced with the Ids of users having this profile when test data is inserted into the test scope. This is particulary useful for users, as we are not really interested in one particular user, but rather in the profile of this user during testing.

At the bottom of Picture 2 you can find ‘User’ and ‘profile.name’, and this is where the process was configured to include replacement strings like ‘{{!Project Member}}’.

Fields CreatedById, OwnerId are preserved in the CSV file – no matter under which user a test is running, inserted records will get user Id values defined in CSV. Being able to define these fields on insert is a feature of Salesforce that needs to be additionally enabled by contacting Saleforce support.

 

Second step. Inserting records into the test scope.

CSV file with test data (see Picture 1) is containing a package of multiple SObjects in one file as well as some utility commands. Values between ‘{{!’ and ‘}}’ are defined in a replacement map which is prepared before the insertion in test data factory class.

 

Special tokens supported:

‘##//SObjectName__c’. As soon as ‘##//’ is found and no ‘include’, ‘Replacement’ or ‘runAs’ next – it is a sign for the system to start SObject records insertion process. The next row defines API names of fields, below the fields names are rows of data. Sorting of objects is determined by a topological sort algorithm.

‘##//include’. Reads another CSV file with a given name.

‘##//Replacement.’. similar to what ‘##//SObjectName__c’ token defines, but is used for generation of replacement map.

‘##//runAs’: executes code with a User with provided profile name. It is helpful to avoid MIXED_DML_OPERATION exception.

 

You can also see ‘testData_TriggerSettings’ mentioned on Picture 1. This is a file reference containing CSV with custom settings records that deactivate triggers. After processing is completed, inserted custom settings will be removed to activate triggers back again.

There was a test data factory class created which queries static resources, creates a replacement map and calls CSV to SObject converter class methods, so that in the end it is sufficient to call one factory method from @testSetup method and test data will be ready to be used aftewards, see Picture 4.

salesforce tests-4
Picture 4. Example of a method call that will import test data from CSV files.

During process of test data creation SObjects are directly accessible. But outside the @testSetup method the only way to access the records is to run SOQL queries. Sharing in static variables is not supported. To simplify the retrieval of records, which is necessary for tests, there is a document inserted with serialized class records containing processing state. This state can be deserialized next, see Picture 5, ‘getCache’ method call. The Ids mentioned on Picture 5 are from a CSV file – an SObject returned from map has different Id, the one it got during insertion.

salesforce tests-5
Picture 5. Example of usage of test data creation artifacts.

 

The processes of handling data packages described above can be used for selective data migration to development sandboxes.

Hopefully you as the reader have found the article useful. The functionality is planned to be released as a managed package, so that it can be accessible to anyone who works with Salesforce and can make development more enjoyable and Salesforce platform moving forward even faster.

 

The following two tabs change content below.

Kiryl Kazlovich

Kiryl war einige Monate einer unserer Salesforce Entwickler, ehe er sich im Jahr 2016 selbstständig machte, um seinen Vorbildern im Silicon Valley nachzueifern.

Neueste Artikel von Kiryl Kazlovich (alle ansehen)

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.