Test Driven Development with Apex on Force.com


As far as I know, Force.com – the software development platform for Salesforce CRM – is the only platform that requires unit test coverage for production code. Before an Apex developer can deploy custom code to a production environment, the overall unit test coverage for the environment must be 75% or better.

What is Unit Test Coverage?

Let’s look at a simplistic, contrived example of unit test coverage. For demonstration purposes, the HelloWorld Class shows an Apex function that tests whether the text of a string matches “Hello World” or not.
// HelloWord Class

public class HelloWorld {
public static String isHelloWorld(String myString) {

if (myString.equals(‘Hello World’)) return ‘Yes it is!’; // Line 1
else return ‘No it is not!’: // Line 2
}
}

The verifyIsHelloWorld test method exercises our gratuitous example

// verifyIsHelloWorld test method

@isTest
private class TEST_HelloWorld {
static testMethod void verifyIsHelloWorld () {
String outcome = HelloWorld.isHelloWorld (‘Hello World’);
System.assert(outcome == ‘Yes it is!’, ‘Expected positive message.’) ;
}
}

At this point, we have 66% test coverage, because our test exercises only one of the two statements in isHelloWorld (line 1).

To bring test coverage up to 100%, we need to add another test method.

// Another test method

static testMethod void verifyIsHelloWorldFalse() {
String outcome = HelloWorld.isHelloWorld (‘Hello Kitty’);
System.assert(outcome == ‘No it is not!’, ‘Expected negative message.’) ;
}


With both of these test methods in play, our code now has 100% coverage.

Why is test coverage important?

Salesforce.com has high standards for its own code and expects custom Apex code to also be robust and error-free. One of the best ways to increase code quality is to encourage developers to write unit tests. Witness a 2005 study found that unit tests increased both coder productivity and code quality.

While it’s possible for developers to boost code coverage with pointless tests, hardcore coders see unit tests as a way to release better code sooner – the keyword being “release”. The time spent on proactive unit testing is a trade-off with the time spent on reactive debugging. We can find our own bugs ourselves with unit tests, or wait and fix them later when a feature comes back with a QA ticket attached.

In my own work, I’ve found that the best way to ensure that code has adequate test coverage to practice test-driven development (TDD).

What is Test Driven Development (TDD)?

For the uninitiated, a classic way to bootstrap unit testing (and TDD) is to start with defect reports. Before fixing a bug, a developer first writes a test that proves that the defect exists. For example, if someone reports that isHelloWorld fails if we pass in a null string, we could start with a test like the one shown by verifyIsHelloWorldNull.

// verifyIsHelloWorldNull

static testMethod void verifyIsHelloWorldNull() {
String outcome = HelloWorld.isHelloWorld (null);
System.assert(outcome == ‘No it is not!’, ‘Expected negative message.’) ;
}


If we run this test, it raises an exception “System.NullPointerException: Attempt to de-reference a null object”.

Since an exception counts as a failing test, we can proceed with the fix, say, by changing the code from:

| if (myString.equals(‘Hello World’)) return ‘Yes it is!’; // Line 1

to:

| if ‘HelloWorld’.equals(‘myString’) return ‘Yes it is!’; // Line 1

and maybe, for good measure, including a fourth test case for an empty string.

// Test for empty String

final static String EMPTY = ‘’;
static testMethod void verifyIsHelloWorldEmpty() {
String outcome = HelloWorld.isHelloWorld (EMPTY);
System.assert(outcome == ‘No it is not!’, ‘Expected negative message.’) ;
}


Once all of our tests are passing, we could even refactor the code, and improve the internal design by using a constant and a single comparison.

// Code refactored

public class HelloWorld {
public final static String HELLO_WORLD = ‘Hello World’;
public final static String YES_WORLD = ‘Yes it is!’;
public final static String NO_WORLD = ‘No it is not!’;
public static String isHelloWorld(String myString) {
return (HELLO_WORLD.equals(myString)) ? YES_WORLD : NO_WORLD;
}
}


If our tests pass (they do), we can be confident that our refactoring did not break the code’s external behavior. Passing tests give us the courage to refine existing code and improve the internal design.

The key idea behind TDD is to “never write a line of code without a failing test”. If we are going to write the test anyway, better to write it first, code to the test, and receive full benefit for the time we invest.

How do we test code that doesn’t exist?

In an Apex environment, a unit test usually operates at the class level. To bootstrap testing a class or method that does not exist, we can start coding the test, create a stub class with stub methods, sufficient to compile the test, confirm that it fails, and then fill-in functionality to pass the test.

Let’s start over from scratch. First, we should define our requirements for the isHelloWorld method.

// isHelloWorld requirements

/**

  • The isHelloWorld method determines if a String equals ‘Hello World’.
  • (1) Given the String ‘Hello World’, the method returns “Yes, it Is.”
  • (2) Given some other String, the method returns ‘No, it is not!’.
  • (3) Given a null or empty string, the method returns ‘No, it is not!’.
    */

    Then, we can write a “happy path” test for the first requirement.


    // A happy path test for requirement (1)

final static String POSITIVE = ‘Expected positive message.’;
static testMethod void verifyIsHelloWorld () {
String outcome = HelloWorld.isHelloWorld (‘Hello World’);
System.assert(outcome == HelloWorld.YES_WORLD,POSITIVE ) ;
}

provide a stub Hello World class to compile the test



// A HelloWorld stub class

public class HelloWorld {
public static String isHelloWorld(String myString) {
return null;
}
}

add just enough behavior to pass one test for one requirement


// Coding requirement (1)

public final static String HELLO_WORLD = ‘Hello World’;
public final static String YES_WORLD = ‘Yes it is!’;

public static String isHelloWorld(String myString) {
return HELLO_WORLD.equals(myString) ? YES_WORLD : return null;
}

then another requirement


// Testing requirement (2)

final static String NEGATIVE =’Expected negative message.’;
static testMethod void verifyIsHelloWorldFalse () {
String outcome = HelloWorld.isHelloWorld (‘Hello Kitty’);
System.assert(outcome == HelloWorld.NO_WORLD,NEGATIVE ) ;
}

// Coding requirements (1) and (2)

public final static String HELLO_WORLD = ‘Hello World’;
public final static String YES_WORLD = ‘Yes it is!’;
public final static String NO_WORLD = ‘No it is not!’;

public static String isHelloWorld(String myString) {
return HELLO_WORLD.equals(myString) ? YES_WORLD : NO_WORLD;
}

and a third


// Testing requirement (3)

static testMethod void verifyIsHelloWorldNull() {
String outcome = HelloWorld.isHelloWorld (null);
System.assert(outcome == HelloWorld.NO_WORLD,NEGATIVE );
}

final static String EMPTY = ‘’;
static testMethod void verifyIsHelloWorldEmpty() {
String outcome = HelloWorld.isHelloWorld (EMPTY);
System.assert(outcome == HelloWorld.NO_WORLD,NEGATIVE );
}

For requirement 3, we added two test methods, but did not need to change any code, since the current implementation passed the tests.

To fully test the method, we might also add a test for a string of maximum length, so that we test both boundaries. But, as it stands, we have 100% test coverage, and a test for each stated requirement, which meets my own personal “definition of done”.

Three takeaways from this exercise are:

  • Never write a line of code without a failing test.
  • Test every requirement, one requirement at a time.
  • Passing tests give us the courage to refactor.
    In a followup blog, TDD with Apex Triggers, we look at how Test Driven Development works in practice, with a real-life non-trivial example.