
Writing test code in Salesforce.com is a necessary step in deploying code. Yes it’s a bit annoying and it sucks when someone else’s test code breaks and prevents you from deploying your code. I saw an email in my inbox from Salesforce about a free webinar on testing and Coverage best proactices, and I’ve always wanted to document some advice that I’m often passing on to other Salesforce Apex Developers when writing test cases, so here I am. I was suprised to not find many blog posts out there when i searched for “Salesforce Apex Testing Best Practices” (I did find one really good blog post at gokubi.com). Hopefully I can add to what’s out there.
Write Portable Tests
I can’t stress this point enough. It’s the top item in that gokubi article too, and YES it’s that important! When writing you should never depend on certain records being there. Even if you think, “No, i’ll make sure no one deletes this record.” One day, when someone else is going to deploy code, it’ll turn up missing.
There’s also a flip side to this. You should also not depend on tables being empty. This can happen when you’re creating code that is supporting a brand new custom object. Usually, I’ll see the test code insert records, then run it’s test case, where the new code will query records from that table and maniplate them in some fashion. This will probably work great, in Sandbox and even deploying into production, but… a month or 2 down the line where there’s more data accumulated in that table and the query no longer behaves as the test expects. No more deployments until this problem is resolved (and of course these are usually discovered when someone is deploying code).
TIP: Sometimes, you need to cheat a bit
This second point in the previous section, about not depending on empty tables, is not always a straight forward change in the test code. Lets take an example of a batch process which queries the entire table and updates a field. When testing batch code, Apex only runs one batch of the process. If you’re creating sample records in your test code, how do you ensure that the batch process will pick up your test records?
You can put in a “sort by” clause in the query to ensure the records can get picked up, but… you really don’t want to affect performance just for the sake of writing test code. I think in this case, it’s ok to cheat a little. Check the Test.isRunningTest flag and modify the query if it’s true. The code below shows an example of an implementation. The class implements a Database.Batchable interface and has a conditional test code in the start method.
global MyClass implements Database.Batchable{
...
global Database.QueryLocator start(Database,BatchableContext bc){
String soqlQuery = "Select Id, Name, MyFlag__c from Account";
if (Test.isRunningTest()) soqlQuery += " Sort By LastModifiedDate Desc";
return Database.query(soqlQuery);
}
...
}
Don’t just write coverage code. Make asserts!
When I see test code without asserts, I always just shake my head. You really should ensure that the code you write is doing what you expect. Writing a proper test case includes creating appropriate test records, then running some code for your test, and then finally putting in place the appropriate asserts will ensure that your code is doing what you expect. It also helps you from making future changes which would break your use case. This is expecially true if you’re writing trigger code, since triggers can affect lots of other apex code and their tests.
Code Structure
For the most part, most of my test code has the following structure (see diagram to the left). This structure may be a bit obvious to most, but I like to explain it to those new to writing test code. It makes code reviews of test code go by much quicker.
A comment should be placed between test code sections. Comments are always helpful, but in the test code, a little mention of the actual use case you are testing for helps enormously!
The asterisk after the “Setup runAs User” is there because this is not always needed. Only if your code is dependant on a specific role / profile or there’s some other User attribute which you need to test your code, would you need this.
A note about using the System.runAs() block. The User object that you pass into the runAs() method does not need to be a inserted into the database. I found this out when some of my test code was deployed into an org with 0 licences free. In my test code I had inserted the user record that I was using in the runAs() block. While fixing my test code, i noticed that inserting the User record is not needed.
Write test code for your most imporant Use Cases
If you can code for all possible Use Cases for your code, that’s great, but sometimes that’s an open ended problem. At the very least you should try to write test code for the most often used Use Cases. You should also code for error cases that could potentially occur a lot. The good thing about coding for both positive and negative use cases, is that it may make you think of more odd use cases, and thus more robust code. Hopefully that will put you very close to 100% code coverage. You may need to code a few more less likey Use Cases, or even directly call some functions and properties to get to that 100% mark. It’s not always necessary to get to the 100% mark, but get as close as you can.
Add a Comment »