I recently guest posted at Fluent C++ about using unit tests to express what your code is doing. That post focused more on the benefits you get from unit testing. It was not as much of a step-by-step guide.
You can view my C++ example (described in that previous post) on Github: cpp-csv-col-replacer.
Steps for Writing Your Own Unit Tests
In this post, I would like to build on what I discussed in that previous post. We are going to look at how to write and run your own unit tests.
- Choose a unit testing framework. If you don’t need any more advanced functionality, I suggest you just stick with Catch based on its simplicity.
- Write a Makefile that can compile both your source and test code.
- You will need a main function for your test code. For Catch, it supplies the main function for us (see mainTest.cpp).
- Your Makefile will need to be able to generate a target that compiles all the source code except for the file with the main function. It will also need to compile all the test code, which should supply a main function. For an example, see my Makefile.
- Choose a specific function or simple behavior in your source code to test. Ideally, the behavior or function will be a simple and only do one thing (hopefully most of your functions already fit this description, but sometimes you may need to refactor an existing function to make it more testable). Identify the inputs and outputs (which may be an exception being thrown) to that function or group of functions.
- Determine a good title for your test case. Begin by using the TEST_CASE macro, giving it that name, and leaving the definition of that test case empty. You should be able to compile and run your test code at this point, and it should indicate “All tests passed” with at least one test case.
- If you aren’t testing a behavior but only a function, you may use a name such as “foo fails when [condition happens]” or “foo passes” or “foo happy path”.
- Testing behaviors generally lead to more descriptive names. For example, “Different num columns (too many) in input data throws exception: ‘input file is malformed’ “ This tests our program’s behavior when a row in the input file has too many columns.
- Once you have determined a good title, fill in the test case’s body. This should look like the normal use case for your code.
- Create any data you need to pass into your function.
- Generally, your function will return what you want to test as its return value or from a variable you passed in by reference.
- You can test the function/variable itself, or use some kind of comparison operator on that function.
- Put that comparison or the test inside a “REQUIRE/REQUIRE_FALSE/REQUIRE_THROWS” macro. These respectively test for true, false, or that an exception was thrown.
- When the test program is run, if any of these failed, you will get different test from “All tests passed”, as you can see in the following screenshot. It will contain details about the failure.
- Repeat steps 3-5 for all the functions and behaviors you want to test.
Over time as you write more and more test cases, this pattern will become familiar, even to the point of becoming second nature. You will also start writing functions in a certain way so they are easier to test, which in turn makes your code clearer.
For several unit test examples, you can see my Github project using C++ code and the Catch unit testing framework.
Let me know if you have any questions!