Unit Testing: The Basics

Posted Sunday 3rd May, 2009

As a web development manager, it’s part of my job to be involved in defining best practices for my team. This means defining a set of standard practices that will benefit the whole team throughout the entire development process. A good example of one such best practice would be code testing.

Testing, for web developers, is usually a fairly disorganised affair. Most of the time, we test “on the job”, i.e., we code something and then we run it; if it breaks, we fix it. In fact, often, the thought of actually structuring our testing process can seem somewhat of an over complication. This is certainly not the case.

The benefits of a structured testing process are legion, as any software engineer will attest. At the very least, it provides confidence in our code – something which can only be of incredible benefit when code is shared across a wider team. At its best, the tests we write can influence the way we develop the code being tested; and save effort, time, and money.

In this post I’m going to look at “unit testing”, and how it can be of benefit to web developers in general.

What is unit testing?

Unit testing is the process of validating and verifying the individual “units” of code in a system. A unit is the smallest testable part of a system or application. In the majority of cases within web development, these units are usually individual functions or methods of our object-oriented back-end and front-end code.

At this point, it’s probably worth noting that unit testing is really only applicable to functional code, such as PHP, JavaScript, Python, Perl, Ruby etc., because it breaks down into units nicely. Testing mark-up and CSS is a slightly different process and one that I may look at in a future blog post.

A note on language

The concept of unit testing is entirely language agnostic, therefore the examples in the following tutorials can be ported to PHP, Perl, Ruby, JavaScript, or any other functional or object-oriented language you are using.

I’ll use Python as the language for all examples in this post as it’s my current weapon of choice (and the interactive interpreter is very handy for trying out ideas); if you’ve never seen Python syntax, don’t worry, it’s easily understandable (if you want more information, take a look at the Python tutorial).

Writing a simple test

To begin with, let’s imagine we have a function for calculating the area of a rectangle:

def get_area_of_rect( width, height ):
    return width * height

Let’s paste this function into the interactive interpreter, so that we can play with it:

>>> def get_area_of_rect( width, height ):    return width * height

Now if we call it, we’ll receive the area of our rectangle like so:

>>> get_area_of_rect( 2, 3 )

Now if we wanted to test this function, we could call it with a some defined parameters and test the output against our expectations:

def test_get_area_of_rect():
    width = 2
    height = 3
    expected_result = 6

    result = get_area_of_rect( width, height )

    if result == expected_result:
        print "Test passed: Received expected result!"

Now if we run our test, we should see the following:

>>> test_get_area_of_rect()
Test passed: Received expected result!

Brilliant; our code passed the test because the expected result was the same as the actual result – however, that’s actually not very useful to us. It’s far more important that our tests can trap failing code than code that works.

Imagine our get_area_of_rect function had been updated as follows:

def get_area_of_rect( width, height ):
    return width / height

Anyone with rudimentary maths skills can tell that this will no longer return the correct area of a rectangle. In fact, if we rerun our test, we’ll get no output:

>>> test_get_area_of_rect()

A fat lot of good that is. If our code is failing for some reason, we want to know about it. After all, that’s the whole reason for testing it.

Let’s update the if statement in our test_get_area_of_rect function:

if not result == expected_result:
    raise AssertionError
    print "Test passed: Received expected result!"

In fact, we could probably dump the else clause since it’s of little real importance except for logging purposes:

if not result == expected_result:
    raise AssertionError

That’s nicer, but Python provides us a shortcut to this if not … raise AssertionError syntax with the assert statement:

assert result == expected_result

With that in mind, the test_get_area_of_rect function should now look like this:

def test_get_area_of_rect():
    width           = 2
    height          = 3
    expected_result = 6
    result = get_area_of_rect( width, height )
    assert result == expected_result

If we run our test again, we should receive more constructive output, considering our get_area_of_rect function is still dividing:

>>> test_get_area_of_rect()
Traceback (most recent call last):
  File “”, line 1, in 
  File “”, line 8, in test_get_area_of_rect

We now have a decent automated test, but we’re only checking a single expected result. If, for instance, we passed in a width of 1 and a height of 1, both multiplication and division would return the same result: 1. This means that we should pass multiple sets of width and height parameters and test against their expected results. Since these parameters will be constant, let’s define them as tuples and loop through them, testing each in turn:

def test_get_area_of_rect():
    known_values = ( ( 0, 0, 0 ),
                     ( 1, 1, 1 ),
                     ( 2, 2, 4 ),
                     ( 2, 3, 6 ),
                     ( 3, 0, 0 ) )
    for width, height, expected_result in known_values:
        result = get_area_of_rect( width, height )
        assert result == expected_result

As you can see, we’re now passing several sets of parameters. Each set of parameters is valid, and as such, we should expect a successful outcome from our get_area_of_rect() call.

Now if we run our test, we should get something like the following:

>>> test_get_area_of_rect()
Traceback (most recent call last):
  File “”, line 1, in 
  File “”, line 8, in test_get_area_of_rect
  File “”, line 2, in get_area_of_rect
ZeroDivisionError: integer division or modulo by zero

Ah ha! By passing multiple sets of parameters we’ve uncovered more significant information regarding the AssertionError that was raised earlier; we have a division by zero error on line 2 of get_area_of_rect. This has supplied us with all the information we need to fix our error, so let’s take a look at that function again:

def get_area_of_rect( width, height ):  # Line 1
    return width / height               # Line 2

Change the division back to multiplication, like so:

def get_area_of_rect( width, height ):  # Line 1
    return width * height               # Line 2

And then rerun our test:

>>> test_get_area_of_rect()

We get no output at all because the test passed successfully. As the old proverb goes, “no news is good news”.

Now if we add to our original code in the future, we’ll still be able to run this testing code to see what has been affected. What’s more, we can add to it by writing more tests, should the complexity grow. In fact, creating an extensive suite of tests is generally the aim of unit testing, but to do so, it’s a good idea to utilise a testing framework, which I’ll look at in the next post of this series.


Writing tests for code enables more efficient debugging, faster bug fixing, and an overall confidence in the quality of your code. Furthermore, the rest of your team (or whomsoever you’re sharing said code with) will equally have confidence thanks to the tests included.

In brief, here’s what we’ve learnt about unit testing:

The next post

In the next post, I’ll be expanding on unit testing by looking at test frameworks, test architecture, code coverage, and other more advanced techniques that can improve your testing environment.

Included in: Development, Python, Testing, Tools, Tutorials, Web


  1. Accessibility
  2. Agile
  3. Ajax
  4. Apache
  5. API
  6. Architecture
  7. Books
  8. Browsers
  9. CMS
  10. CouchDB
  11. CSS
  12. Design
  13. Development
  14. Django
  15. Email
  16. Events
  17. Gaming
  18. Grammar
  19. Hardware
  20. HTML
  21. HTTP
  22. Humour
  23. Idea
  24. Information Architecture
  25. JavaScript
  26. jQuery
  27. Lean
  28. Life
  29. Linux
  30. Literature
  31. Mac OS X
  32. Management
  33. Meme
  34. Microformats
  35. Monday
  36. MySQL
  37. Networking
  38. News
  39. Personal
  40. Photoshop
  41. PHP
  42. Process
  43. Python
  44. Reference
  45. REST
  46. Science
  47. SEO
  48. Server
  49. Site
  50. Sitepimp
  51. Social
  52. Spelling
  53. Syndication
  54. Testing
  55. The Future
  56. Thoughts
  57. Tools
  58. Tutorial
  59. Tutorials
  60. Typography
  61. UI
  62. UNIX
  63. Virtualisation
  64. Web
  65. Web Standards
  66. Widgets
  67. Wii
  68. Writing
  69. Xbox
  70. XHTML