I talk a lot about unit testing. And why shouldn’t I? Unit testing is the key to writing clean, maintainable code. If you concentrate on writing code that is easily testable, you can’t help but end up with decoupled, clean, high-quality code that is easy to maintain. What’s not to like?
But sometimes, there are questions over the definition of terms when it comes to unit testing. For instance, what, exactly, is a “unit”? What does “mocking” mean? How do I know whether I actually am doing unit testing? In this article, I try to clear up some of those definitions.
What is a “Unit”?
The first question that comes up when discussing unit testing is, well, what is a unit? You can’t do unit testing without knowing what a unit is.
When it comes to unit testing, I view a “unit” as any discreet module of code that can be tested in isolation. It can be something as simple as a stand-alone routine (think StringReplace or IncMonth), but normally it will be a single class and its methods. A class is the basic, discrete code entity of modern languages. In Delphi, classes (and records which are conceptually very similar) are the base building blocks of your code. They are the data structures that, when used together, form a system.
In the world of unit testing, that class is generally referred to as the “Class Under Test (CUT)” or the “System Under Test”. You’ll see those terms used extensively – to the point where it is strongly recommended that you use CUT as the variable name for your classes being tested.
Definition: A unit is any code entity that can be tested in isolation, usually a class.
Am I Actually Doing Unit Testing?
So when you are doing unit testing, you are generally testing classes. (And for the sake of the discussion, that will be the assumption hereafter…) But the key thing to note is that when unit testing a class, you are unit testing the given class and only the given class. Unit testing is always done in isolation – that is, the class under test needs to be completely isolated from any other classes or any other systems. If you are testing a class and you need some external entity, then you are no longer unit testing. A class is only “testable” when it’s dependencies can be and are “faked'”, and thus tested without any of its real external dependencies. So if you are running what you think is a unit test, and that test needs to access a database, a file system, or any other external system, then you have stopped doing unit testing and you’ve started doing integration testing.
One thing I want to be clear about: There’s no shame in doing integration testing. Integration testing is really important and should be done. Unit testing frameworks are often a very good way to do integration testing. I don’t want to leave folks with the impression that because integration is not unit testing, you shouldn’t be doing it – quite the contrary. Nevertheless, it is an important distinction. The point here is to recognize what unit tests are and to strive to write them when it is intended to write them. By all means, write integration tests, but don’t write then in lieu of unit testing.
Think of it this way: Every unit test framework – DUnit included – creates a test executable. If you can’t take that test executable and run it successfully on your mother’s computer in a directory that is read only, then you aren’t unit testing anymore.
Definition: Unit testing is the act of testing a single class in isolation, completely apart from any of it’s actual dependencies.
Definition: Integration testing is the act of testing a single class along with one or more of its actual external dependencies.
What is an Isolation Framework?
Commonly, developers have used the term “mocking framework” to describe code that provides faking services to allow classes to be tested in isolation. However, as we’ll see below, a “mock” is actually a specific kind of fake class, along with stubs. Thus, it is probably more accurate to use the term “Isolation Framework” instead of “Mocking Framework”. A good isolation framework will allow for the easy creation of both types of fakes – mocks and stubs.
Fakes allow you to test a class in isolation by providing implementations of dependencies without requiring the real dependencies.
Definition: An isolation framework is a collection of code that enables the easy creation of fakes.
Definition: A Fake Class is any class that provides functionality sufficient to pretend that it is a dependency needed by a class under test. There are two kind of fakes – stubs and mocks.
If you really want to learn about this stuff in depth, I strongly recommend you read The Art of Unit Testing: With Examples in .Net by Roy Osherove. For you Delphi guys, don’t be scared off by the C# examples -- this book is a great treatise on unit testing, and gives plenty of descriptions, proper techniques, and definitions of unit testing in far more detail than I’ve done here. Or, you can listen to Roy talk to Scott Hanselman on the Hanselminutes podcast. I openly confess that this blog post is a faint echo of the great stuff that is included in both the book and the podcast. If you really want to get your geek on, get a hold of a copy of xUnit Test Patterns: Refactoring Test Code by Gerard Meszaros. This heavy tome is a tour de force of unit testing, outlining a complete taxonomy of tests and testing patters. It’s not for the faint of heart, but if you read that book, you’ll know everything there is to know and then some.
A stub is a class that does the absolute minimum to appear to be an actual dependency for the Class Under Test. It provides no functionality required by the test, except to appear to implement a given interface or descend from a given base class. When the CUT calls it, a stub usually does nothing. Stubs are completely peripheral to testing the CUT, and exist purely to enable the CUT to run. A typical example is a stub that provides logging services. The CUT may need an implementation of, say, ILogger in order to execute, but none of the tests care about the logging. In fact, you specifically don’t want the CUT logging anything. Thus, the stub pretends to be the logging service by implementing the interface, but that implementation actually does nothing. It’s implementing methods might literally be empty. Furthermore, while a stub might return data for the purpose of keeping the CUT happy and running, it can never take any action that will fail a test. If it does, then it ceases to be a stub, and it becomes a “mock”.
Definition: A stub is a fake that has no effect on the passing or failing of the test, and that exists purely to allow the test to run.
Mocks are a bit more complicated. Mocks do what stubs do in that they provide a fake implementation of a dependency needed by the CUT. However, they go beyond being a mere stub by recording the interaction between itself and the CUT. A mock keeps a record of all the interactions with the CUT and reports back, passing the test if the CUT behaved correctly, and failing the test if it did not. Thus, it is actually the Mock, and not the CUT itself, the determines if a test passes or fails.
Here is an example – say you have a class TWidgetProcessor. It has two dependencies, an ILogger and an IVerifier. In order to test TWidgetProcessor, you need to fake both of those dependencies. However, in order to really test TWidgetProcessor, you’ll want to do two tests – one where you stub ILogger and test the interaction with IVerifier, and another where you stub IVerifier and test the interaction with ILogger. Both require fakes, but in each case, you’ll provide a stub class for one and a mock class for the other.
Let’s look a bit closer at the first scenario – where we stub out ILogger and use a mock for IVerifier. The stub we’ve discussed – you either write an empty implementation of ILogger, or you use an isolation framework to implement the interface to do nothing. However, the fake for IVerifier becomes a bit more interesting – it needs a mock class. Say the process of verifying a widget takes two steps – first the processor needs to see if the widget is in the system, and then, if it is, the processor needs to check if the widget is properly configured. Thus, if you are testing the TWidgetProcessor, you need to run a test that checks whether TWidgetProcessor makes the second call if it gets True back from the first call. This test will require the mock class to do two things: first, it needs to return True from the first call, and then it needs to keep track of whether or not the resulting configuration call actually gets made. Then, it becomes the job of the mock class to provide the pass/fail information – if the second call is made after the first call returns True, then the test passes; if not, the test fails. This is what makes this fake class a mock: The mock itself contains the information that needs to be checked for the pass/fail criteria.
Definition: A mock is a fake that keeps track of the behavior of the Class Under Test and passes or fails the test based on that behavior.
Most isolation frameworks include the ability to do extensive and sophisticated tracking of exactly what happens inside a mock class. For instance, mocks can not only tell if a given method was called, it can track the number of times given methods are called, and the parameters that are passed to those calls. They can determine and decide if something is called that isn’t supposed to be, or if something isn’t called that is supposed to be. As part of the test setup, you can tell the mock exactly what to expect, and to fail if that exact sequence of events and parameters are not executed as expected. Stubs are fairly easy and straightforward, but mocks can get rather sophisticated.
I’ve written about the Delphi Mocks framework in a previous post. It takes advantage of some cool new RTL features in Delphi XE2. It’s also a very generous and awesome gift to the Delphi community from Vince Parrett, who makes the very awesome FinalBuilder. If you have XE2 and are doing unit testing, you should get Delphi Mocks and use it. If you don’t have XE2 and are doing unit testing, you should upgrade so you can start using this very valuable isolation framework.
But again, the whole point here is to test your classes in isolation; you want your CUT to be able to perform its duties without any outside, real, external dependencies.
Thus, a final definition: Unit testing is the testing of a single code entity when isolated completely from its dependencies.