Teach Me Salesforce

A community approach to learning salesforce.com

Author Archive

Writing Good Test Methods

with 7 comments

Yesterday Rebecca (@sfdc_nerd) posted about her first trigger.   She did a great job and even got 100% coverage from her test class. Rebecca had actually posted this on her own blog: A force behind the force a few days earlier and I noted that she needed to do just one more thing in her test class to make it a real test. The one thing she didn’t do was check that the trigger actually created the record it was supposed to create and we sorted that out with a phone call and an email. Afterwards I started thinking that it would be good to explain what a test method really needs to be “complete” and figured I would post it here.  I have come up with a slightly contrived example below to illustrate some extra points.

I often see test classes written which achieve the necessary code coverage but aren’t exactly “tests”. Salesforce.com requires at least 75% code coverage to deploy code to a production environment. Some developers then see 75% as the goal and are happy once they cross that threshhold when they should really be shooting for 100%. Now, if you have spent any time doing development in Salesforce, you know that 100% coverage is sometimes impossible, but we should at least aim for 100%.

With that in mind, I wanted to dissect a simple test class to explain (in my opinion at least, please feel free to chime in) what you should be aiming to achieve when writing a test class.

Consider the following trigger which creates a task to call the lead when the lead is created. The due date of the lead should be 7 days from today unless a lead for a member of the very important ‘Elephantman’ family calls in. Then we will make the task due tomorrow (sorry about the formatting, wordpress doesn’t like me today).

trigger newLeadTask on Lead (after Insert) {

 List<Task> taskList = new List<Task>();
 for(Lead l : trigger.new){
 Task t = new Task(Subject = 'Contact Lead',
 WhoId = l.id,
 Status = 'Not Started', 
 OwnerId = l.OwnerId);

 if(l.LastName <> 'Elephantman'){
 t.ActivityDate = Date.today()+7;
 }else{
 t.ActivityDate = Date.today()+1;
 }
 taskList.add(t);
 }

 insert taskList; 
}

In order to test this, we must write a class which will insert a lead. Inserting the lead will cause the trigger to fire and the code will be executed.


@isTest
private class testLead{
    private static testmethod void testLeadActivity(){
        Lead l = new Lead(LastName = 'Randomname', Company = 'ABC, Inc.');
        insert l;
    }
}

Running this test we see that we get 87% coverage of our trigger. Awesome, we are ready to deploy to production because we are above 75%. Well, yes, but we are aiming for 100% here if we can get it.

We need to think of the test method as sort of a stand in for a human tester. If you asked your colleague to test this trigger, you would expect them to create a lead. Once the lead was created you would then expect them to actually check that the task was created. Well, let’s do that. After we insert the lead, let’s query for the newly created task; we should expect that 1 (and only 1) exists and that it has a date 7 days from today:

@isTest
private class testLead{
    private static testmethod void testLeadActivity(){
        Lead l = new Lead(LastName = 'Randomname', Company = 'ABC, Inc.');
        insert l;

        List<Task> taskList = [SELECT id, ActivityDate from Task WHERE whoId = :l.id];
        System.assertEquals(1,taskList.size());
        System.assertEquals(Date.today()+7,taskList[0].ActivityDate);
    }
}

The first System.assertEquals statement equates to our colleague checking that the task was actually created, the second statment checks that the date is correct. Great, so now we have a valid test but we still haven’t tested everything. The logic of the trigger says that if a lead is created for a member of the ‘Elephantman’ family, then the task due date is the next day. We expect our colleague to test both use cases, so we will do the same.

@isTest //indicates this is a test so that the code below doesn't count against our quota
private class testLead{
 private static testmethod void testLeadActivity(){
 //insert a random (non Elephantman lead)
 Lead l = new Lead(LastName = 'Randomname', Company = 'ABC, Inc.');
 insert l;

 List<Task> taskList = [SELECT id, ActivityDate from Task WHERE whoId = :l.id];
 System.assertEquals(1,taskList.size());
 System.assertEquals(Date.today()+7,taskList[0].ActivityDate);

 //insert an Elephantman lead
 Lead eMan = new Lead(LastName = 'Elephantman', Company = 'ABC, Inc.');
 insert eMan;

 List<Task> eManTaskList = [SELECT id, ActivityDate from Task WHERE whoId = :eMan.id];
 System.assertEquals(1,eManTaskList.size());
 System.assertEquals(Date.today()+1,eManTaskList[0].ActivityDate);
 }
}

When I run this test I get 100% coverage and I know that I have fully tested all use cases for this trigger. I can now deploy this to production.

Beyond being good practice, there is a very good reason to write your test classes as completely as possible. On a recent salesforce.com Webinar, I heard it mentioned that Salesforce actually runs ALL, yes ALL, test methods across all orgs before upgrading them to a new release. By writing your test methods properly, you are helping ensure nothing breaks when Salesforce updates to a new version.

Advertisements

Written by knthornt

May 17, 2011 at 9:30 am

Posted in Apex, Intermediate, Trigger

Updating reports with Eclipse and the Force.com IDE

with 9 comments

I came across a question on the Salesforce.com Answers forum today and thought the answer might be useful to others.

Here is the original question: http://bit.ly/k3nKHD

The question concerned updating reports after a new Stage was created on the Opportunity Object.  Once the new stage was created and Opportunity records had been updated to use that new stage, any reports leveraging the old stage in their filters would be broken.  The poster of the question was hoping there was a faster way to identify the reports that filtered on that stage than opening each report individually.  I suggested using Eclipse and the Force.com IDE along with Notepad++ to get things done.  This will work with much more than just Stage, so insert your own search terms as desired.

The following assumes that you are already comfortable with Eclipse and the Force.com IDE:

The first thing you will want to do is download all of your report metadata to Eclipse.   If you have reports that are in Production and not your Sandbox then you will need to connect to your Production environment or refresh your Sandbox first.  If you do need to connect to Production just BE CAREFUL.

  • Create a new project
  • Include ‘reports’ or more specifically the report folders you are concerned with
  • Let Eclipse refresh the project with the reports metadata
Now that you have all of the metadata for your reports, it is time to search through the metadata for the StageName to find all of the reports using that Stage.
  • In Eclipse, navigate to the reports folder you just download. Right click and select properties
  • Highlight and Note (or copy) the Location. This is the path where the metadata files are located
  • Open a windows explorer window and navigate to the path you just copied
You should now see all of the folders containing your report metadata.
  • Open Notepad++ click Search > Find in Files  (or just Ctrl+Shift+F)
  • Enter your search term (in our case, the Stage) , make sure the ‘In all sub-folders’ box is checked and click the ‘Find All’
  • A message will pop up saying “Searching… Press Enter to Cancel”. Don’t touch anything, just let it do it’s search, this may take a little while depending on how many files you have.  Clicking OK will cancel the search.
You should now have a list of files where the Stage has been found.  You can use this to narrow down the reports that need to be updated and hopefully cut down the time you spend updating your reports.
Note: It is possible to update your reports directly from the IDE as well.  If you choose to do that, make sure you back up the report data to another location in case something goes wrong.

Written by knthornt

May 4, 2011 at 11:03 am

Posted in Advanced, Configuration

Tagged with