Wednesday, June 26, 2013

Unit Testing: What's a Unit?

We all know that we should be writing unit tests, but have you ever thought about what a unit is, exactly? Most definitions out there, including the one on Wikipedia, describe it as the smallest piece of code that can be tested.

But what does that mean? The answer to that question will have an influence over what you test and how you test. Let's start small, and work our way up.

Expressions

Whatever a unit of code is, in order to test it, it has to at least have an output. It has to be able to evaluate to something. For this reason, we might consider the smallest testable unit of code to be an expression.

For example, in the statement y = x * 3, the expression x * 3 evaluates to something, depending on what x is. We have an input and an output – that is, x, and the value that gets assigned to y, respectively. We can say, "given the input of x, the value of y should be such-and-such..."

But, having inputs and outputs is not enough to actually test it. For example, if this expression is in the middle of a function somewhere, we can't tell our testing code to pull only that expression out of the middle of that function and test it. In other words, in order to test it, you have to be able to execute it by itself. Thankfully, in this case, we can make a simple change to do that:

Easy! All we did was take the expression we want to test, and put it in its own function. And now we can easily test that expression with different inputs and make sure we get the expected outputs, simply by calling the function that wraps it.

So we've moved from an expression to a function.

Functions

Functions are very testable. They have inputs and outputs, and you can call them from your test code. In an object-oriented world, they often also have contexts. For example, a function inside a class is called a method. Methods could have references to other methods and variables inside the object. Here's an example:

Now, the result of getArea() depends on the value of this.length. In a way, it still has inputs and outputs, but the input – length – isn't passed into the method. It's grabbed off of the class.

It's harder to isolate this kind of function for testing because the result depends on its context.

Classes

This brings us to classes. At this level, we have the methods plus the context – that is, the functions along with associated data. We now have a unit of code that's testable, even if the output from some functions depends on instance data. And that's why most of the thought leaders in the object-oriented community regard the class as the unit.

Functions Again...

Still, I really want to think of a function as the smallest testable unit. It's just so measurable – it has inputs, outputs, and it's executable. The problem is just that doggone context. If your test code could just configure the context for the function – if it could extract the function from the class, and provide it with a new, stubbed out context – then the function could be the smallest testable unit.

How can you configure the context? Well, it depends on the language. For example, JavaScript allows you to exercise functions with apply() or call(). They allow you to change what the this context resolves to. For other, more object-oriented languages, you might not actually be able to separate a method from its class... and in my opinion, you really shouldn't go out of your way to try. It's good to respect the philosophy of your language.

So in short, the unit in a unit test is generally considered to be the class, although it's also possible to regard the function as the unit. It just depends on the language.

No comments:

Post a Comment

Profile Picture
Dave Leeds
My Hobbies:
  • Programming
  • Cartooning
  • Music Writing
Full Profile