Introduction To Test Driven Development in Apex

Introduction To Test Driven Development in Apex

I’m going to talk about test driven development.  Specifically, how you can use it on the force.com platform to improve the quality of your code.

I’m a massive fan of using TDD with Apex.  To be frank, I don’t understand how people write Apex code without TDD. Debugging is a slow, painful process on the platform.  If you employ TDD in your approach, you’ll never have to worry about System.debug again!

The objective of this blog post is not to discuss unit testing.  I’m going to assume that you already know how and why we write unit tests on the force.com platform (or any platform, really).  I will be doing some more posts on unit testing in general at a later date, because I’m boring like that.  This article on developerforce is a great overview of unit testing basics if you need some pointers.

We’re going to focus on the process of using Test Driven Development to build a robust, high quality piece of software.

Here’s a brief introduction to TDD, and then we can get into a coding exercise using the age old classic – a roman numeral converter.  I bet those crazy romans didn’t know we’d still be using their silly numbering system in the year MMXV!

I’ve published the code for this post on GitHub, complete with a button you can click to deploy the apex classes straight into your developer edition.  I’ve structured the code in such a way that each scenario has a failing test for you to fix, then another version of the class is given that shows you the solution.  Please check it out and give the exercise below a go for yourself.

Test Driven Development Introduction

I’m not going to go into too much detail about what TDD is. There’s lots of information out there on this already, so I’ll just summarise it by saying that it’s a software development process which involves writing tests to determine whether your code is behaving as expected.  Specifically, the main difference between TDD and just writing unit tests for minimum code coverage is that in TDD you write your unit test before you write your code, and then you write/update your code to make your test pass.

The high level process for developing a piece of code with TDD is as follows:

  1. Write a test, asserting that your method provides the desired outcome
  2. Run your test.  At this point the test will fail because the code is not written/updated yet
  3. Write (or update) your code to fix the failing test.  The code written should only focus on making the test pass, we should not start predicting further functionality.  A completely inelegant solution is acceptable as long as your test passes
  4. Run your test(s).  The test should now pass, and you can be confident that the new code meets the requirement.  Any existing tests should still be passing
  5. Refactor your code.  If your code is looking shoddy or inefficient, now is the time to refactor it.  If you break anything while refactoring, your test(s) will fail, alerting you immediately
  6. Repeat the process.  Add a new test, which addresses the next code requirement.

A nice little mnemonic that has always helped me remember this process is “Red/Green/Refactor”.  When you have written your test you should get a red light because the test should fail.  Then you update the code so that the test passes, giving you a green light.  Once you have a passing test, it is safe to refactor your code with minimum risk of breaking everything.

Why should we care about TDD?

Unit testing on force.com always appears to be an afterthought.  We HAVE to have at least 75% coverage in order to deploy to production, so why not make writing those tests part of your development process and reap the benefits instead of having a mad panic at the end of your project, trying to get your coverage up to 75%?

There are a lot of advantages to TDD, here are a few:

  • Shortens development feedback time
  • Creates a detailed specification – other developers can look at your unit tests to see how your code behaves (or how it SHOULD behave if it’s broken)
  • Less time debugging and reworking code
  • Immediate red flag if you break code while refactoring
  • Forces you to really think about what you’re trying to achieve – When I use TDD, I find I write much better, more concise code to solve my problem.  It has to be tried to be seen, but by building your code incrementally, addressing one requirement at a time, you’ll work much faster
  • When one of your colleagues adds a trigger and breaks your code, you’ll know about it immediately and can hand out the applicable punishment in record time.  If your team get into the practice of running all unit tests frequently, they’ll know immediately that they’ve broken someone else code
  • Code coverage – you will never have to worry about it again.  Using TDD, your code will always 100% covered, or very, very close to it.

One criticism I hear about TDD is that it’s slow.  This is rubbish.  With a decent set of tests, you will rarely waste time trying to track down bugs and debug code.  Going through that red/green/refactor process will have you writing better code, solving the problem in smaller steps, and will highlight immediately how and why your code is failing.  I can’t remember the last time I sat down for hours putting a load of system.debug() lines in my code to try and track down a bug.  My tests are usually granular enough to point out the exact problem.

will caveat the above with a point though:  when you first start working with TDD, it will feel slow.  It takes a while to settle into the mindset.  It also takes a while to learn how to write good, efficient unit tests.  I can assure you though, stick it out and you’ll be bashing out tests and working code in record time.

Speaking of bugs – TDD can really help you out there too.  When a user reports a bug to me, this is my process:

  1. Add a new unit tests that recreates the conditions of the bug, and asserts the CORRECT outcome.
  2. Run all tests – your new test should fail
  3. Refactor your code to fix the bug
  4. Run your tests again – all tests should now pass

Automatic regression testing is a nice added benefit.  Never again will you have to worry that your quick hack of the code to fix an urgent bug has broken something else.  If it had, you would have tests failing.

TDD with the Roman Numeral Converter

Let’s address an easy to understand problem with TDD.  We want to create a method which can convert a roman numeral string (such as IX) to an integer (9 in this case), and return that integer.

The rules for converting roman numerals are fairly easy to grasp, but we’re not going to try and create a fully functioning converter here.  Let’s imagine we have sat down with our future users, and they have come up with the following “business requirements”:

  1. When the text “I” is entered, return 1
  2. When the text “II” is entered, return 2
  3. When the text “III” is entered, return 3
  4. When the text “IV” is entered, return 4
  5. When the text “V” is entered, return 5
  6. When the text “VI” is entered, return 6
  7. When the text “VII” is entered, return 7
  8. When the text “VIII” is entered, return 8
  9. When the text “IX” is entered, return 9
  10. When the text “X” is entered, return 10

In a real system, we’d have some more colourful requirements I’m sure.  But these ones are perfect for our TDD approach.

We need to cycle through our high level process further up the page for each of these requirements, adding a new test for each requirement as we go.

First, we need to create our class and method.  We will just have it return null for now:

public class NumeralConverter
{
    public static Integer ConvertNumeralToInteger(String numeral)
    {
        return null;
    }
}

Now we need our first unit test, which should check that when we pass the text “I” into the method, it returns the number 1.

Two things to note with my tests:

  1. I name my tests in the format <methodname>_what_should_happen
  2. I use the Arrange Act Assert structure in my tests

Both of these points are discussions for another post, but thought I’d point them out.

Here’s our first test:

private static testMethod void ConvertNumeralToInteger_should_convert_I_to_1()
{
    // Arrange
    String testNumeral = 'I';
 
    // Act
    Integer result = NumeralConverterV1.ConvertNumeralToInteger(testNumeral);
 
    // Assert
    System.assertEquals(1, result);
}

Let’s run the test. Here’s the result:

Failing unit test

The test was expecting the number 1 to be returned, but the method just returned null.  Let’s fix that.

Remember, we’re not going to start masterminding a full solution, we just want to write the minimum amount of code to get the test passing.  Here’s my solution:

public static Integer ConvertNumeralToInteger(String numeral)
{
    return 1;
}

Now let’s run our test again…

Passing Test 1

Nice! One test down, nine to go…

Next we add our second test for the requirement “should return 2 when passed ‘II'”

private static testMethod void ConvertNumeralToInteger_should_convert_II_to_2()
{
    // Arrange
    String testNumeral = 'II';
 
    // Act
    Integer result = NumeralConverterV2.ConvertNumeralToInteger(testNumeral);
 
    // Assert
    System.assertEquals(2, result);
}

Run the test, and it should fail. Now we update our code to pass the second test.  Again, we ONLY address the requirement of the new unit test.

Here’s the new version:

public static Integer ConvertNumeralToInteger(String numeral)
{
    if (numeral == 'I')
        return 1;
 
    return 2;
}

Our new test results:
Passing unit test

Let’s whiz through our next unit test, and then our update to the code to make the test pass:

private static testMethod void ConvertNumeralToInteger_should_convert_III_to_3()
{
    // Arrange
    String testNumeral = 'III';
 
    // Act
    Integer result = NumeralConverterV3.ConvertNumeralToInteger(testNumeral);
 
    // Assert
    System.assertEquals(3, result);
}
public static Integer ConvertNumeralToInteger(String numeral)
{
    if (numeral == 'I')
        return 1;
    else if (numeral == 'II')
        return 2;
 
    return 3;
}

Now, I’ve just run my tests and all three are passing. However, the code is starting to look a bit ropey now.  I can see a much easier way to satisfy my requirements so far. I want to tidy it up, and this is where TDD really comes into its own.

This example is fairly straightforward and changing the code is not rocket science, but in a more complex system it can be pretty scary refactoring code that is currently working.

Because we’ve written specific unit tests for all of our business requirements so far, if we break anything while refactoring, we’ll know immediately.

Let’s tidy up…

public static Integer ConvertNumeralToInteger(String numeral)
{
    return numeral.length();
}

Have we broken anything? Apparently not…

Screen Shot 2015-04-04 at 00.59.35

I think we’re getting the idea now.  We write a test that fails, we update our code to fix the test, we run the test to make sure our code changes were successful (as well as making sure we haven’t broken any of our other tests), then we start again.

After our third test was written, we were able to confidently refactor our code in the knowledge that we had test code covering every scenario that we had catered for so far.

It probably seemed like a lot of work to get to a single line of code, but I deliberately used a simple problem so that we could focus on the process, not the solution.

The unit tests themselves are also very basic in this post.  In a real system, your unit tests is going to include setting up tests data in the database, calling a method, and then asserting that the desired outcome occurred (records were created, a web service was called etc).  I will be doing some blog posts on unit testing in general.

That’s it, you’re now a TDD expert (almost).  Take a copy of my code from GitHub and give the exercises a try in a developer org.  I’ll also accept pull requests if they add benefit to the code, or if someone wants to go the whole hog and put a working solution in place.

Any questions or comments, come and see me on Twitter.

Leave a Reply

Your email address will not be published. Required fields are marked *