Do you Unit?

What is Unit Testing?

In simple terms, unit testing is a definition of tests to run on your code. Usually, these tests are automated. The "unit" in unit testing means that the tests should test the smallest functional units, like functions or methods. However, unit testing does not have to be limited to only testing small parts; it can also test interactions between parts with complex combinations and variables, and even test some visual elements, like whether or not a certain image is actually shown or if a class was applied to an XHTML tag.

Unit testing has a lot of benefits, which are described here: https://en.wikipedia.org/wiki/Unit_testing

However for me, the biggest benefit to unit testing is just forcing me to slow down and think. When I write a unit test, I end up asking myself two main questions: What do I expect to happen, and how can I test this?

What do I expect to happen?

When I write a unit test for a method, in addition to others, I almost always test three things:

  • What do I expect if I give valid data?
  • What do I expect if I give invalid data?
  • What do I expect if I don’t give any data?

At the simplest level the things you expect should be obvious. But, have you tested them? You would be surprised how quickly you will discover something that does not do what you expected. Let me give you a concrete example:

Recently, I wrote some code for an object that generated a set of radio buttons for a web form. The object also validated the values that it got back.

The first code I wrote checked that a value was returned if the response was required. Then I wrote tests for this code. My first test simply set the object’s required attribute to true and set the submitted value to an empty string: i.e. "required, empty". In this case, the validation method is supposed to return false, and my test showed that it did. I wrote three other tests for "required, not empty", "not required, empty", and "not required, not empty". All the tests passed.

Table 1: First test of required
empty not empty
required expected: false
actual: false
expected: true
actual: true
not required expected: true
actual: true
expected: true
actual: true

The next code that I wrote tested that the value returned was one of the values that was sent out. In other words, if I created radio buttons with values of 1, 2 and 3, I should get back 1, 2 or 3, not 5 or “Two”. I wrote tests for valid and invalid values.

Then something interesting happened when I ran all of my unit tests. The earlier test for “not required, empty” failed! How could that have happened?

Table 2: Second test of required
empty not empty
required expected: false
actual: false
expected: true
actual: true
not required expected: true
actual: false
expected: true
actual: true

It turned out my new code only accepted values that had been sent out and wouldn’t accept an empty value, even if the response was not required. Fortunately because of the automated tests, I caught this right away.

I think this example demonstrates one of the greatest strengths of automated unit testing. It is easy to run every test, every time. That way you can often catch when a change you made in one place causes unexpected changes someplace else. If I had just tested the form by hand, I would have run the first set of tests once and then not bothered to run them later, assuming that everything still worked.

How can I test this?

Essentially, unit tests are a functional implementation of a specification. “"If the function gets these variables, it should return this." "If the user enters this value, the application should do this." If you can write a sentence like this, you should be able to write a unit test for it. Let me give you another example:

On the same project, I had to modify a page for deleting a student. One file contained all of the code: conditional logic, database queries and HTML output. It took a while just to understand what the page did when given different input, but eventually I was able to make these statements:

  • If a student is enrolled in a current course, show a warning message.
  • If a student has grades for any course, show a warning message.
  • If a student is not enrolled in a current course and a student does not have grades for any course, create a delete button.

Which could be translated into these four tests:

Table 3: Tests for the student deletion page
has grades no grades
current course expect: course warning
& grade warning
expect: course warning
no current course expect: grade warning expect: delete button

The specification above did not address every possible state of the page, but they would cover most of the cases that my modifications would affect. In addition, testing these different states by hand would be an enormous pain. Each time I wanted to do that, I would have to take steps like these:

  • Create a student record
  • Enroll the student in a current course
  • Test "no grades, current course"
  • Add a grade for the student
  • Test "has grades, current course"
  • Remove the student from the course
  • Test "has grades, no current course"
  • Remove grades for the student
  • Test "no grades, no current course"
  • Delete all the test records I created

There is no way I would have the time or enthusiasm to do all of those steps every time I wanted to test the site. On the other hand, it wasn't that hard to write an automated script to do the same steps for me, and then I could run the tests in just one click of the button.

More importantly, the tests I wrote had an immediate benefit. One of the tests I described above failed. When I checked, it turned out I had incorrectly used AND instead of OR in an SQL statement. If I hadn't tested so thoroughly that mistake might have gone into production, creating a bug that would be very difficult to track down later on.

Excuses, excuses, excuses …

"But," you say, "what if we don’t have a detailed specification? Heck, we often don’t have any written specification." Then this is the perfect time for unit tests, because the tests become a living specification. With unit tests you are forced to stop and say, "This is how we expect it to work." Invariably, the projects without good specifications are also the projects where the client says, "I need this to work differently," at the last minute. Again, unit tests come to the rescue because those rushed, eleventh hour hacks are always the ones that will cause an unexpected problem later on. If you wrote your unit tests early, ran them regularly and kept them up to date, you have a much, much better chance of determining where that problem is going to rear its ugly head.

As stated in the link above, unit tests don’t test everything, but if you find yourself always using excuses like, "My code is too complicated for unit tests," or "We don’t have time," then you are actually making your job harder. It may seem that you don’t have time to do unit tests, but they force you to write small, functional, reusable, maintainable, testable pieces of code. I have been programming for twenty years now, and I promise you, every hour you spend on writing tests and tracking down the errors, will save you a day 's worth of work next month and a week’s worth of work next year.