The Silver Lining

Lessons & Learnings from a salesforce certified technical architect.

Salesforce Unit Tests & Code Coverage

with 13 comments

Unit testing *sigh*. Oh how they vex me. If they weren’t so important (and required) I’d just skip the lot, but they are and so we – champions of software development – must press on in the face of dreary complexity; we will not back down, we will not surrender, we will look that CRT/LCD screen in the pixels and say, “Untested units, you will not defeat me!”.

Diving into the thick of things, the most common question seems to be, “Why can’t I get code coverage for my entire class?!”. The trick here is to think like a runtime engine, and consider how you might journey through all possible testing paths. Now I never said it’s easy, but with a bit of practice (and 8 truck loads of patience) you’ll get there. Let’s look at some common cases.

The If-Else Interrogation

This situation comes about when we have a piece of code something like this,

public class MyClass{

	public Integer var{get;set;}

	public void aMethod(){
		if(var==1){
			// Operations for this value
		} else if (var==2){
			// Different operations for this value
		} else {
			// And more operations for all other values
		}
	}

}

If we assume that the variable called ‘var’ is set by a field on a VisualForce page (during normal operation), our test methods have to mimic this behaviour and set the it programmatically. Not only this, but we need to set the variable ‘var’ to each of the values required to step through each branch of the if-else statement. Further to this, I would suggest a test method per value that ‘var’ can assume. Alot of work I know, but easier for our silly human brains to comprehend, as well as simpler for our programmer hands to maintain. Test code for the above might look so,

public static void testMethod testAMethod1(){
	MyClass mc = new MyClass();

	// Mimic that variable being set by the page
	mc.var = 1;

	mc.aMethod();

	System.assert(...); // Assert that the operations in the first branch worked
}

public static void testMethod testAMethod2(){
	MyClass mc = new MyClass();

	// Mimic that variable being set by the page
	mc.var = 2;

	mc.aMethod();

	System.assert(...); // Assert that the operations in the second branch worked
}

public static void testMethod testAMethod3(){
	MyClass mc = new MyClass();

	// Mimic that variable being set by the page
	mc.var = 9;

	mc.aMethod();

	System.assert(...); // Assert that the operations in the last branch worked
}

The for-Loop Congruence

Our code sample in this case might be,

public class MyClass{

	public List<Account> accounts{get;set;}

	public void aMethod(){

		// Debug should go here: System.debug('accounts:'+accounts);

		for(Account a: accounts){
			// Insert logic here
		}
	}

	public void anotherMethod(){

		for(Account a: [SELECT id FROM Account WHERE condition = true]){
			// Insert logic here
		}
	}
}

If you’re not getting code coverage in situations like those in aMethod() and anotherMethod(), you probably have an empty-list situation i.e. the list ‘accounts’ is empty, or the ‘SELECT id FROM Account WHERE condition = true’ query is not returning any values. A few well placed System.debug() messages will tell you where you’re going wrong.

The Exception Fluctuation

The last – and trickiest – case I’ll cover is exception handlers. The reason they’re more complex is that you not only have to test that your code works with expected input (and situations), but that it deals with errors gracefully (something we should all be doing anyway). Let’s have a look at an example situation,

public class MyClass{
	public String accountName{get;set;}

	public void aMethod(){
		try{
			Account a = [SELECT id FROM Account WHERE name = :accountName];
		} catch (System.queryException e){
			// Deal with exception in logical and superfun way
		}
	}
}

I would suggest writing a single test method to cover the try-part, and a separate test method to cover the catch-part,

public static void init(){ 	// This is an aside. I recommend setting up your own test data so that any unit tests
							// are independent of the Org that you have them in.

	Account a = new Account(name='A name that I know doesn\'t exist in my real dataset');
	insert a; // The data inserted will on exist (and be available) for the lifetime of the test method that executes it.

}

/** Positive Test **/
public static void testMethod testAMethod1(){
	init();

	MyClass mc = new MyClass();

	mc.accountName = 'A name that I know doesn\'t exist in my real dataset';
	mc.aMethod();

	List<Account> a = [SELECT id FROM Account WHERE name = 'A name that I know doesn\'t exist in my real dataset'];
	System.assert(a.size()>0);
}

/** Negative Test **/
public static void testMethod testAMethod1(){
	// No call to init() since we want an exception to be thrown
	MyClass mc = new MyClass();

	mc.accountName = 'A name that I know doesn't exist in my real dataset';
	mc.aMethod();

	List<Account> a = [SELECT id FROM Account WHERE name = 'A name that I know doesn\'t exist in my real dataset'];
	System.assert(a.size()==0);
}

Testing exceptions can be further complicated where you need to catch more than one type of exception,

public void aMethod(){
	try{
		Account a = [SELECT id FROM Account WHERE name = :accountName];
		a.name = 'A new name';
		update a;
	} catch (System.QueryException e){
		// Deal with exception in logical and superfun way
	} catch (System.DmlException e){
		// Deal with this exception differently
	}
}

Here I’d suggest we have a test case for the try-part, as well as one for each of the catch-statements. This makes dev life a bucket-load more boring; take heart though, things could be worse, at least you’re not a banker.

These samples are relatively simple, but you’ll notice that even your most complex code is mostly an assortment of these situations. One feature of good software development is being able to break a complex problem down into tiny bits and analyse those independently, so get out your hard-hat and microscope, and let’s get our testing-thang on.

About these ads

Written by Wes

February 4, 2010 at 6:06 pm

13 Responses

Subscribe to comments with RSS.

  1. Great article Wes. I didn’t enjoy writing Apex unit tests until I learned the following:

    * Always run tests from Eclipse
    * Follow the Eclipse code coverage report for untested lines
    * Create classes specifically for testing
    * Use the @IsTest attribute on test classes and methods so the lines of code don’t count against org limit

    -Mike

    Mike Leach

    February 4, 2010 at 8:41 pm

    • Very good points, thanks Mike. On your first point I’d even be so bold as to say, if you’re working in a team, and aren’t a n00b developer, all coding should be done from within Eclipse.

      Wes

      February 5, 2010 at 10:25 am

  2. testing is way more fun if you do test-driven development. but yea, deep code branches can be a chore.

    naaman

    February 5, 2010 at 1:00 am

    • Indeed! Test-driven development FTW. For those who haven’t heard of TDD before wikipedia says, “Test-driven development (TDD) is a software development technique that relies on the repetition of a very short development cycle: First the developer writes a failing automated test case that defines a desired improvement or new function, then produces code to pass that test and finally refactors the new code to acceptable standards”.

      Wes

      February 5, 2010 at 10:26 am

  3. Very nicely done, ole chap! I very much fancy your article. I think the big take away from your article for newcomers is “…would suggest a test method per value that ‘var’ can assume…”. Like you said it can seem tedious but that’s the best way to test all variations without going crazy. Would love to see Salesforce use something like JUnit with setup and tear down methods. Thanks for the info!!

    Jeff Douglas

    February 5, 2010 at 3:15 pm

  4. A couple of useful advices in here! Indeed cutting down your test classes into smaller parts will help you when you test class suddenly fail.
    And I would like to emphasize the fact that Eclipse IDE Test plugin is not as good as it should be for professional development (no method selection, filter don’t work properly, the output window is one the worst, the double click on an error works once every 2 years etc….).
    A quick look at the JUnit integration in Eclipse show us how it should be.

    Morongroover

    February 10, 2010 at 10:43 am

    • Ah, good ol’ JUnit.. man those were the days;)

      Wes

      February 10, 2010 at 10:52 am

  5. Just getting to this article now, and it was a good read with some great takeaway information.

    Writing test classes are always the worst part of my day, but yes, at least i am not a banker. :)

    Reppin505

    December 29, 2010 at 1:09 am

  6. [...] everybody’s shared resources. It does this by making sure your code has test coverage. Tons and tons of stuff has been written about test coverge, including inventive ways to get around it. I’ll [...]

  7. This article was great. Saved my morning from being wasted banging my head against the wall while trying to figure out why my for and if statements didn’t have coverage.

    Josh Harbert

    June 9, 2011 at 12:57 pm

    • It’s always great to know that I’ve been able to help :)

      Wes

      June 9, 2011 at 1:14 pm

  8. Very helpful!

    But I’m searching for an answer to another question:

    Why are the test percentages in Eclipse different than the percentages that come up when running all tests in the Salesforce front end? For example, I have 3 classes with coverage over 75% when running tests in Eclipse, but they are considerably lower when running all tests (~50%).

    Have you found this on any of your projects?

    Thank you!

    Anthros

    November 26, 2011 at 7:47 am

    • There’s a bug in the wild – I’ll post about it this week but for now just clear all test history. That’s probably it…

      Wes

      November 30, 2011 at 5:03 pm


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 2,196 other followers

%d bloggers like this: