Five simple guidelines for unit testing
In my opinion, writing unit tests is not about discovering bugs as much as it’s a tool for designing your code in a robust way. Unit tests are in principle small tests for nimble isolated parts of your code, such as a class or even just one method.
Writing unit tests is a design process
The unit test defines a recipe of how the isolated code is supposed to respond to the given input, and thereby it is a way of designing your application.
Defining the expected outcome of a class or method with a unit test is often a lot easier than actually implementing the code that should produce the outcome, therefore unit tests are cheaper to edit or even throw away than implemented code.
By following a Test-driven Development principle, you always write a failing test before you implement anything, and if those tests are good, you will maintain a high level of test coverage on all the different components that make up your awesome application. Bugs happen either because you got some input you didn’t expect, or because the different units (with green tests) does not work together as intended. These kinds of bugs are better discovered with automated integration tests, or manual testing.
Good vs. Bad unit tests
A little over a decade ago, Michael Feathers made a good list of some features a unit test should not include. He argued that a test is not a unit test if:
- It talks to the database
- It communicates across the network
- It touches the file system
- It can’t run correctly at the same time as any of your other unit tests
- You have to do special things to your environment (such as editing config files) to run it.
By following these, you get good unit tests, and you also benefit from having fast tests. Having a fast test suite is essential when following TDD.
I found some examples of RSpec model specs for a Rails application, on which I think that the principles for unit testing should apply.
This first example tests a scope
on the User
class for finding only active users. It requires database access, and would fail in a
weird edgecase where some other unit test create a user at the same time. While some might argue that testing scopes this way is a
necessary evil, this is better tested with a integration test. We should assume that the scope
functionality provided by ActiveRecord
is properly tested already.
This is a good example of a good unit test. Although the example is simple, it provides a clear description of how the #name
method
on an instantiated User
should behave, and does not violate any of the five rules. If you want to be explicit, you should also write
some test-code for how the method should behave if first_name
and/or last_name
is nil
.
Recent Posts
Extend ActiveStorage::Blob with callbacks
ActiveStorage is currently missing both validations and callbacks, but you can easily extend it with the callbacks you need.
Writing a custom analyzer for ActiveStorage
Writing custom analyzers for your ActiveStorage blogs is not well documented, but quite easy. This is how I implemented a simple EDI file analyzer for my neverending hobby project.
Manjaro/Arch: transfer packages to another computer
How to make a backup of all installed packages on a Arch/Manjaro distro, and install them on a different machine.