A unit test is a way of testing a unit - the smallest piece of code that can be logically isolated in a system. In most programming languages, that is a function, a subroutine, a method or property. The isolated part of the definition is important. In his book "Working Effectively with Legacy Code", author Michael Feathers states that such tests are not unit tests when they rely on external systems: “If it talks to the database, it talks across the network, it touches the file system, it requires system configuration, or it can't be run at the same time as any other test."
Modern versions of unit testing can be found in frameworks like JUnit, or testing tools like TestComplete. Look a little further and you will find SUnit, the mother of all unit testing frameworks created by Kent Beck, and a reference in chapter 5 of The Art of Software Testing . Before that, it's mostly a mystery. I asked Jerry Weinberg about his experiences with unit testing -- "We did unit testing in 1956. As far as I knew, it was always done, as long as there were computers".
Regardless of when and where unit testing began, one thing is for sure. Unit testing is here to stay. Let's look at some more practical aspects of unit testing.
What Do Unit Tests Look Like?
A unit can be almost anything you want it to be -- a line of code, a method, or a class. Generally though, smaller is better. Smaller tests give you a much more granular view of how your code is performing. There is also the practical aspect that when you test very small units, your tests can be run fast; like a thousand tests in a second fast.
Consider this sample code:
def divider (a, b)
Using Ruby, those small tests might look something like this:
class smallTest < MiniTest::Unit::testCase
assert_equal(3, divider(a, b))
This example is overly simple, but it gives you an idea of what I mean by small. Small tests also have the benefit of making it harder to cross systems -- from code into a database, or 3rd party system. Strictly speaking, there isn't anything wrong with crossing systems, but there are consequences like gradually slowing your tests. A few years ago I worked for a company where this crept into the test set, eventually we had thousands of tests, set up and tear down scrips for the database, and also a test suite that took hours to run.
Who Should Create The Unit Test Then?
In his book, Real Time Business Systems, Robert V. Head says "Frequently, unit testing is considered part of the programming phase, with the person that wrote the program...unit testing". That isn't because programmers hold the secret sauce to unit testing, it's because it makes sense. The programmer that wrote the prod code will likely know how to access the parts that can be tested easily and how to mock objects that can't be accessed otherwise. It's a time trade off.
Other times, someone will come in after the fact and write tests to help create safe guards while they refactor or further develop that area of the code base.
What Can I Do With Them?
Hammers are great tools and can help you with lots of different jobs -- opening car windows or turning off alarm clocks. But, there are especially well suited to putting nails through hard surfaces. Unit tests are similar. They can do lots of different things, they should probably only do a few.
Test Driven Development
Test Driven Development, or TDD, is a code design technique where the programmer writes a test before any production code, and then writes the code that will make that test pass. The idea is that with a tiny bit of assurance from that initial test, the programmer can feel free to refactor and refactor some more to get the cleanest code they know how to write. The idea is simple, but like most simple things, the execution is hard. TDD requires a completely different mind set from what most people are used to and the tenacity to deal with a learning curve that may slow you down at first.
Checking Your Work
TDD isn't new, but at this point it is still mostly for the go getters. The rest of us are checking our work. Writing unit tests after you have written the production code may be a more traditional way of doing it, but it is no less useful. It's also something you're familiar with if you have been in a math class any time in the past ten years.
After your work is checked and it is clear that the code is doing what you think it is doing, the value of the unit tests change a little bit. Tests that can be easily run with every build of your product act as change detection notifying you when code changes in unexpected ways.
Code documentation is a drag, and it shows, mostly by how little code documentation gets written. Unit testing can make the documentation burden a little easier by encouraging better coding practices and also leaving behind pieces of code that describe what your product is doing. Rather than having to constantly feed the documentation beast with code change, you'll be updating a system of checks that is working for you.
There are a few uses of unit testing that you will want to avoid when possible. Creating integration tests that cross system borders and touch databases or 3rd party systems can be done, but this quickly results in a test suite that takes longer and longer to run with each added test. There are plenty of test frameworks out there that specialize in higher level testing. If you want to test larger pieces of your product at a time, you might want to investigate those other frameworks.
One other risky area is end to end tests. These usually require careful ordering, dependencies on other tests, and careful set up to get your system in a special 'test ready' state. Much like integration testing, there are plenty of tools to choose from made just for this purpose.
You definitely can do these things with unit frameworks, but it might quickly become more work than it is worth.
The most common problems we have with unit testing usually aren't technical problems.
People have a hard time adapting to new ways of working after spending time in an environment where unit testing amounts to loading up the latest build and seeing whether it starts or not can be difficult. Test infected groups are a cultural phenomenon. I have had the most success in changing how testing is done by finding one person that is interested and invested in staying up to date. That person can be your champion to help build a case for the usefulness of unit testing and help to spread the idea through the dev organization through her successes. (http://dilbert.com/strip/2011-03-24)
There is also a myth that says if testing is good, then more testing is better. Smart testing is good and will help to create a valuable, stable product. But smart testing doesn't always end in an impressive number of tests. Smart unit testing delivers relevant information about your software quickly and often.
We've covered some of the basics of unit testing, and given you some ways to talk about them with your team.
The next step is to start your own, preferably small, tests.
If you enjoy unit testing with TestComplete, TestLeft can be a great addition to your testing environment. TestLeft allows you to create unit tests in any IDE, generate application models for webpage or desktop applications in just two clicks, and allows you to accelerate your shift left movement with BDD.