What Is A Unit Test?
In his highly recommended book, ‘The Art Of Unit Testing’, Roy Osherove describes a unit test as ‘an automated piece of code that invokes a unit of work in the system and then checks a single assumption about the behaviour of that unit of work.‘
It’s a good explanation. Let’s pick it apart. In places, I will layer my interpretation on top of Roy’s definition.
‘an automated piece of code…‘
It’s Automated; not manual. Manually testing a system is not automatic and therefore never a unit test. A computer runs a suite of unit tests. And Yes, it’s more code for developers to write. It’s up to us to produce the code for the unit tests and the implementation. Unit tests are meant to be meaningful and understandable by the developers who maintain the implementation code. To that end, a unit test should be written in the same computer language as the implementation. Imagine how awkward and difficult it would be to switch languages between unit tests, written in Python, but the system code written in C#.
‘…invokes a unit of work in the system…‘
Real-world systems do many things. They carry out many functions. For a unit test, we want to focus on one such system function and exercise it. For the sake of clarity, this does not mean we are restricted to calling only the highest-level functionality in the system, say, like creating a new customer. Systems perform lower-level, detail-oriented functions, say, hashing a password, and they too are units of work. Anything a system can do is somewhere exposed as a public function. Each unit test focusses on exercising one ‘thing’ a system does, or one unit of work, at a time.
‘…checks a single assumption about the behaviour of that unit of work.‘
Whenever we test a system, whether manually or automatically, in order of it to be a useful test, we must have certain expectations as to how the system should respond. For example, when we try to log in with invalid credentials, then the system will deny us access. A given input for an action will produce an expected result. It’s how predictable systems work. What Roy is saying here is that we should pick one output, one assumption, and check that. For example, a call of a high-level workflow method might perform these steps:
- Try and get some information from a repository (e.g. database), and
- If there was no data, then exit early, or
- If there was data, make some changes to that data, and
- Try and save that data back to the repository.
Each of these steps is an assumption and ought to be separately tested via a unit test:
- Do we try and get data from the repository?
- If we didn’t get data, do we exit early?
- If we did get data, do we make the correct changes to it?
- Do we try and save the altered data back to the repository?
These are independent steps and so deserve at least one unit test each.
I feel that there is a bit more to unit tests which we will examine next time.