The joys of unit testing
Some background
The benefits of unit testing are well-known to software engineers. However, they are less well-known to researchers and scientists.
Based on my own experience, I suspect their avoidance of unit tests stems from some misconceptions:
- “Unit tests entail too much overhead in time and effort”
- “Unit tests aren’t necessary because my code is simple”
These concerns may be true—unit tests only become useful if the codebase becomes sufficiently large. However, the threshold for usefulness can be surprisingly small. Heuristically, I’d put it at around 50 lines of code.
Benefits of unit testing (from my experience)
I’ve worked in environments with a strong emphasis on unit testing—business ventures and research laboratories.
However, I’ve only started incorporating unit tests into my PhD research within the past year or so. The benefits have been remarkable. I’ll list some of them here:
- Unit tests force me to think clearly about the problem my code is solving.
- In order to write a test, I have to work out one or more simple problems by hand.
- Unit tests can encourage good software design
- Modularity
- Simple building blocks with well-defined behaviors
- Unit tests have greatly accelerated development for my research projects.
- They help me catch bugs in my new features as I write them
- They’ve been extremely helpful for navigating code edits
- The helpfulness increases with the size of edit.
- Unit tests are a programmer’s best tool for reasoning through significant code refactors.
Based on my work experience, unit tests are also indispensable for collaborative development. They provide a notion of “correctness” that developers must satisfy whenever they submit new code. For similar reasons, unit tests help me confidently resume work on code I wrote, after not touching it in months.
Unit testing in Julia
It’s very easy to write unit tests for a Julia package.
Julia comes with the Test
unit testing package pre-installed.
You can climb the learning curve for Julia unit tests in about 15 minutes. I’ll give a sketch of how it works.
Suppose you’re writing a Julia package called YourPackage
.
It has the typical directory structure:
YourPackage/
Project.toml
Manifest.toml
src/
YourPackage.jl
other_file.jl
<et cetera>
These are the steps to add unit tests to YourPackage
:
- Make a
YourPackage/test
directory:YourPackage/ Project.toml Manifest.toml src/ YourPackage.jl other_file.jl <et cetera> test/
- In the
YourPackage/test
directory, make a newruntests.jl
file:YourPackage/ Project.toml Manifest.toml src/ YourPackage.jl other_file.jl <et cetera> test/ runtests.jl
- Write your unit tests in the
runtests.jl
file. It may look like the following example:
using YourPackage
using Test
@testset "adder tests" begin
@test adder(1,2) == 3
@test adder(-5, 7) == 2
end
@testset "subtractor tests" begin
@test subtractor(5,5) == 0
@test subtractor(5,-1) == 6
end
Here we’re pretending YourPackage
exports two functions: adder
and subtractor
.
Notice that we must include the using YourPackage
and using Test
lines.
Each test uses the @test
macro, followed by a Boolean statement.
Tests can be organized into @testset
s, labeled for clarity (e.g., “adder tests” and “subtractor tests”).
Once these elements are all in place, you can run your tests from the Julia command prompt.
- Press
]
to enterPkg
mode - Run the
test
command:
_ _ _(_)_ | Documentation: https://docs.julialang.org
(_) | (_) (_) |
_ _ _| |_ __ _ | Type "?" for help, "]?" for Pkg help.
| | | | | | |/ _` | |
| | |_| | | | (_| | | Version 1.6.2 (2021-07-14)
_/ |\__'_|_|_|\__'_| | Official https://julialang.org/ release
|__/ |
(YourPackage) pkg> test
Julia will execute runtests.jl
as a script, and report the results of each @testset
it encounters.
\( \blacksquare\)