DEV Community

Cover image for C++ unit testing with Catch2 🧪👨‍🔬
Batuhan Ipci
Batuhan Ipci

Posted on • Updated on

C++ unit testing with Catch2 🧪👨‍🔬

Writing unit tests for my static site generator - palpatine, taught me an entirely new testing framework called Catch2. For my Hacktoberfest pull request this year I had done a unit testing contribution for a repository and I am currently working on building a microservice for my cloud computing class which requires us to use Jest for JavaScript unit testing. So, I can say that I have some experience with unit testing and continuing to develop my skills in it.

Building good unit testing skills takes time as it requires us to think about our code more deeply. What should the input look like? What is the expected output and what are some of the edge cases? In my normal, day-to-day, personal projects, I did not pay much attention to testing. But I know that it is important for real-world development. 

Trust the code 🌱

As the name suggests, unit testing is about testing small units of code or components of an application or system to ensure that it is behaving the intended way. An application has building blocks that work in harmony with one another. By testing out these building blocks or units and checking that it is behaving the right way - we can trust the code! 

What if some codes are hard to test? Well, that means that it probably needs to be broken down. This also means that unit testing encourages modularity. 

If a unit of code uses another unit as a dependency we also need to ensure that the dependent unit is being used the right way. 

Why choose Catch2? 

There are many established testing frameworks for C++ such as Google Test, Boost.Test, Cute, CppUnit, and many more. However, what sets Catch2 apart from the others is its ease of use and simpler learning curve. Not just that. Catch2 also has great documentation and tutorials that I could read and follow.  Also, over the summer, I completed a Udemy course on CMake and there I found a template for testing with Catch2. That template was really useful for me in setting up the configurations to get started. 

As a beginner in writing tests, I would say Catch2 was not as simple for me to learn as more experienced programmers suggest. I had to spend a few days reading through the documentation, going over YouTube videos, and looking through many many examples online before I could write my first test. But once I started writing, it felt easier to go on. 

As stated in Catch2's documentation: 

Catch2's main advantage is that using it is both simple and natural. Test names do not have to be valid identifiers, assertions look like normal C++ boolean expressions, and sections provide a nice and local way to share set-up and tear-down code in tests.

Setting it up 🛸

I first had to start by downloading the catch2.hpp file and then #include it

 

#include <catch2/catch_test_macros.hpp>

Enter fullscreen mode Exit fullscreen mode

And then I had to define this on top.

 


#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do


// this in one cpp file

Enter fullscreen mode Exit fullscreen mode

The TEST_CASE macro is used for introducing test conditions. It is used to group the test cases for a particular unit of code.

SECTION is defined beautifully in their documentation

Another way to look at sections is that they are a way to define a tree of paths through the test. Each section represents a node, and the final tree is walked in depth-first manner, with each path only visiting only one leaf node.


TEST_CASE("CLI Parser works perfectly", "[parser]") {

std::vector<std::string> vct{"./palpatine"};

SECTION("Without any args") {

REQUIRE_THROWS_AS(get_options(vct), std::runtime_error); 
}

Enter fullscreen mode Exit fullscreen mode

As mentioned earlier, my CMake course provided me a template to work with that I could modify and use for my testing purposes. If you check my CMakeLists.txt file you will see:

 


if(ENABLE_TESTING)

set(TEST_MAIN

"unit_tests")
set(TEST_SOURCES

"main.cpp")

SET(GCC_COVERAGE_COMPILE_FLAGS "-fprofile-arcs -ftest-coverage")

SET(GCC_COVERAGE_LINK_FLAGS "--coverage")

SET( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${GCC_COVERAGE_COMPILE_FLAGS}" )

SET( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${GCC_COVERAGE_LINK_FLAGS}" )

add_executable(${TEST_MAIN} ${TEST_SOURCES})

target_link_libraries(${TEST_MAIN} PUBLIC

${PALPATINE_LIB}

argparse

Catch2::Catch2WithMain 
)
add_test(NAME ${TEST_MAIN} COMMAND ${TEST_MAIN})
endif(ENABLE_TESTING)

Enter fullscreen mode Exit fullscreen mode

Test coverage 💯

My project also has a test coverage provider that shows the lines that are covered by the test and the percentage of coverage. For my test coverage provider, I chose to use gcovr. It had clear documentation on setting it up and I could do it without facing any trouble.

From their documentation.

Gcovr provides a utility for managing the use of the GNU gcov utility and generating summarized code coverage results.

The command for running the test coverage is:

gcovr 
Enter fullscreen mode Exit fullscreen mode

As you can see in the image below, I am able to see the lines covered by my tests!

Image description

If you want your coverage to look colorful, just pip it with lolcat 😃

Image description

Final thoughts ☕︎

Writing tests for C++ was harder compared to the other projects I have worked on. As the codebase evolves, I need to also make sure that the tests get updated over time.

Oldest comments (1)

Collapse
 
saminarp profile image
Samina Rahman Purba

Great post!