The title of this post actually isn’t a question that I get asked very often. I think the reason that I don’t get asked this very often is because of two different reasons. The first reason is that people (and programmers especially) are embarrassed to admit when they don’t know something that they feel like they should know. Secondly, many developers out there think they know exactly what you are talking about when someone says “unit testing”.
Of those developers who think they know what unit testing actually means, I would be willing to bet that only about 10% really do. You see, the problem is that when most developers are asked to define unit testing they will say something like “tests that are run against a project using a unit testing framework”. That is kinda right… well… not really. It isn’t really right at all. In fact, calling frameworks like NUnit, xUnit, MbUnit, MSTest, etc… “unit testing frameworks” is inaccurate. They are instead “automated testing frameworks” which can be used to execute any number of kinds of tests. One of these kinds of tests just happens to be unit tests. There are also integration tests, performance tests, load tests, etc…
Okay, so what exactly is a unit test? It is a test which verifies a “unit”, or the smallest piece of an application which is able to be tested. Ideally, when writing a unit test in C#, you’d want to isolate an individual class and write tests against only its functionality. The developers doesn’t want to pull in external dependencies and doesn’t want to access external processes or services. Unit testing is all about testing small pieces in isolation.
So why would we want to write tests like this? Many reasons…
They are fast. Have you ever worked in a system that mixes unit tests and integration tests? How long did they take to run? I have worked in a handful of systems that weren’t very large and still had suites of tests that took 10+ minutes to run. How often do you think that developers are going to run these? Not very often at all, and being able to run your unit tests quickly and easily is very important to having quick feedback while developing. Forcing developers to sit through minutes of tests will guarantee that your tests will rarely get run. A broken test found after a developer has moved on to another task will take them substantially longer to fix than one they find right away.
They are reliable. If all of your tests only operate in memory and don’t touch the disk, go across the network, query from a database, etc… then we can be assured that these tests will, for the most part, run smoothly. You won’t have to deal with numerous different settings, connection strings, external processes, etc… Tests which don’t run reliably, or can break frequently will merely frustrate developers and keep them from running them. Remember, tests are useless if they aren’t run.
They are accurate. By this I mean that since they only test very small pieces of functionality it is often quite easy to tell what has broken when the test fails. In a test that crosses a wide swatch of the application, it can often be much harder to figure out where the breakage actually occurred. Having pinpoint tests makes sure that you can find your regressions quickly.
They are flexible. Applications are full of unexpected events. But if they are unexpected, then how can we make them happen in a reliable manner? How can we force a database to throw an exception during a query? Well, in many cases we probably can’t. These are the cases where unit tests allow us to fake dependencies in order to raise exceptions or to cause situations that would otherwise not likely come up during normal execution. Sometimes we don’t care about these events, but sometimes we do, and when we do we need to have some way to force them so that we know how our application is going to respond.
Does this mean that if we are writing unit tests then we don’t need to write integration tests? Of course not! What it does mean is that integration tests need to be separated out from your unit tests so that they can be run in a single environment away from the developer’s desktop. Ideally this would occur on the build server. This way the developer doesn’t have to worry about long running or brittle tests. If all of the unit tests passed, but the integration tests didn’t, then the developers can take the time to figure out why these tests are executing correctly.
This is all great, why isn’t everyone writing unit tests? Well, unfortunately unit tests aren’t a cure-all. Many people complain that writing unit tests is a burden that they just don’t have time for. In fact, I came across a post today that said just that. I think that this is a myth that is generally driven by poorly designed software. Now I know that this is a generalization that is made quite frequently, and one that is even harder to back up, but I really think that this is the truth.
In an ideal system you would be writing unit tests as you are writing your classes, and then you would writing your integration tests as you integrate them in with the rest of the system. In some larger systems, units tests can be unavoidable. Often different pieces of the system are being written before their dependencies have even been created. Or they are created by separate teams that are simply developing against an interface. In these cases the unit tests would be written long before integration tests are even possible.
Another complaint that I often see is people complaining about brittle unit tests because they are using facilities such as MSTest’s accessors which allow developers to directly tests private methods (which I personally think is very bad practice, but that is another rant). Either that or they haven’t factored out methods in classes enough and they are trying to test way too much. Getting used to testing public interfaces of classes and not relying on internal implementations will go a long way toward having less brittle tests.
Automated testing is an indispensible tool, and unit testing is just one of the tasks that you should be performing with it. Testing your application from top to bottom is very important, but making sure that the individual pieces of your application work in isolation is often the only way to reliably and quickly exercise a lot of the code in your application.
Next time you are writing an application, give it a shot. Split out your unit tests and integration tests so that they can be easily run separately. You may not notice any advantage at first, but once your suite of tests grows large you’ll find that you are able to write new code much faster because you have almost instant feedback on regressions in your tests.