So what’s a good unit test look like?

Here at Imdex, we write a lot of automated tests. A lot. Having a high quality suite of automated tests is part of our strategy for being able to move quickly and build products that work.

There’s lots of kinds of automated tests, but in this blog post I’m going to talk specifically about unit tests.

Unit testing is a software development process in which the smallest testable parts of an application, called units, are individually and independently scrutinised for proper operation.
Source: https://zeroturnaround.com

Unit tests are important because they give you confidence. When you have a suite of good unit tests, you can refactor your code without breaking things. You can add features without creating new bugs.

But writing good unit tests isn’t easy. I’ve seen a lot of teams waste a huge amount of time and energy writing unit tests that really don’t help them much.

So what do ‘good’ unit tests look like?

Good unit tests are independent and isolated

They test one thing at a time, ideally with one assertion. They don’t cause side effects. They certainly don’t rely on side effects. You can run them in any order and they still pass. They don’t depend on anything except the unit of code under test. They don’t access global state, the file system or a database.

Good unit tests are descriptive

When a test fails you should be able to look at its name and immediately know what part of the code is broken. There are countless conventions you can use to name your tests. Here are a few I’ve seen around

  • MethodName_StateUnderTest_ExpectedBehavior
  • MethodName_ExpectedBehavior_StateUnderTest
  • Should_ExpectedBehavior_When_StateUnderTest
  • When_StateUnderTest_Expect_ExpectedBehavior

As long as the name can tell you what it’s testing, the rest is just a matter of taste. So pick one and stick to it.

Good unit tests are repeatable

Good unit tests pass every time you run them, whenever you run them, wherever you run them. Sometimes I find teams living with tests that fail occasionally on a continuous integration server. Or teams living with tests that only pass in one timezone so every developer around the world has to set their PC to that arbitrary timezone. These unreliable tests are poisonous. They waste time, confuse new developers and reduce your confidence in your test suite.

Good unit tests are fast

The longer it takes to run a test, the less likely it is to be run. To save time, developers will try to sneak more and more features into the code between running the tests. Developers will also just avoid writing tests if they take too long to run. In general, any unit test that takes longer than half a second to run needs to be looked at. Unit tests should run fast because they’re isolated. If you find a slow test, it’s normally because the test is doing something it shouldn’t like accessing the file system, a database or a network.

Good unit tests test units

This sounds pretty obvious, but it’s surprisingly easy to get wrong.

Sometimes it’s hard to isolate a single class or module to be tested. But don’t be tempted to test 2 units in one test. You’ll permanently couple them together and you’ll never get the same level of test coverage.

It’s also possible to test at too low a level. Some languages like JavaScript don’t have access modifiers to protect the internal implementation of a unit. So you’ll find a JavaScript unit test testing a helper function. The problem with testing the internals of a unit is that it makes your tests brittle. You won’t be able to refactor the private implementation of a public function without re-writing your tests.

Good unit tests are not ignored

When tests break, you need to fix them. Don’t ignore, mute or comment them out. Tests that don’t run aren’t tests, they’re just dead code.

Good unit tests are well structured

I like to write unit tests with the Arrange, Act, Assert pattern. With this pattern every unit test is written in 3 parts:

Arrange: Setup the prerequisites for your test. Create objects, prepare inputs and configure mocked interfaces.

Act: Perform the actual work of the test, typically by calling a function.

Assert: Verify that the unit under test did what you expected. Did it return the right thing, or throw the correct exception? Ideally this should only require writing a single assert.

Conclusion

Unit tests are the foundation of a good test strategy so it’s important to get them right. Good unit tests give you confidence to build a clean and reliable code base. Bad unit tests are just a waste of time.

And remember if you haven’t tested it, it probably doesn’t work.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.