Unit Testing CLI Programs in Go
It’s a common scenario — for me at least — to be building a CLI tool (main
package) that has CLI options (flags
package) that I want to add unit tests to.
Here’s a simple example of a CLI tool that adds or multiplies some input numbers:
Let’s try it:
5 + 7 + 9 = 21
— great… But wait! 3 * 2 * 5 != 0
…I’ve put a deliberate bug in the command. Something that we will catch and fix with a unit test.
Don’t Try This At Home Kids
Let’s write a unit test that uses the same CLI arguments so we can fix the bug. However, we have a lot of global state that needs to be manipulated:
Yuck! This is really ugly and problematic code… miles away from a small, easy to read unit test.
One fairly obvious solution here is to separate the logic that does the calculating from the main()
function (that handles the arguments and output), like:
Now the unit test is trivial:
But this is cheating! Since we have bypassed the flags completely, we are not actually testing the CLI arguments. We’d really like to perform black-box testing on the tool as it would be used — instead of testing isolated parts. Especially if a bug was introduced to the way flags are parsed.
A Better Solution
- We now have an
out
which makes it much easier to capture output. - We no longer use the functions in the
flag
package. Instead we have to instantiate a new flag parser for each call tomain()
. - Anywhere output is generated will have to write to
out
instead.
Now our unit test needs only know about the CLI arguments before calling main()
:
This looks a lot nicer when there are multiple tests: