It is time to show some code in our blog. We would like to present you one of the solutions for running tests in compile time by using CMake build system.
All project sources can be found on Quick Turn Studio Github profile here.
Project structure
Firstly, let’s look at the project structure. In general overview we can distinguish three parts:
- application,
- static library with the logic that we would like to test,
- test application.
Each part has own CMakeLists.txt
file.
Project
| CMakeLists.txt # creating our application
| main.cpp
| third parties directory
| CMakeLists.txt
| testing framework # Google Test in our case
| prime checker library
| CMakeLists.txt # creating static library
| production Code # *.h and *.cpp files
| directory with tests
| CMakeLists.txt # creating test executable
| tests code # *.cpp files with tests
CMakeLists.txt of our library
The library consists of just two files: PrimeChecker.h
and PrimeChecker.cpp
. Of course, no surprise, they contain function which checks if provided number is a prime number. However, more important for us is content CMakeLists.txt.
project(PrimeCheckerLib)
# Creating static library
add_library(PrimeCheckerLib STATIC PrimeChecker.cpp)
target_include_directories(PrimeCheckerLib INTERFACE .)
# Add directory with test project to build system
add_subdirectory(tests)
This is a simple code, we hope nobody is surprised about it. In 4th line, we just create a static library. Line nr 6 is important to us, because build system will allow us to include headers automatically whenever we link to PrimeCheckerLib
.
CMakeList.txt of test executable
More magic happens in testing project.
set(TEST_NAME PrimeCheckerTests)
project(${TEST_NAME})
# creating test application
add_executable(${TEST_NAME} PrimeChecherTests.cpp)
# linking to tested library
target_link_libraries(${TEST_NAME} PRIVATE PrimeCheckerLib)
# linking to Google Test Framework
target_link_libraries(${TEST_NAME} PRIVATE gtest gtest_main)
# creating target to run test during compilation
add_custom_target(Run_${TEST_NAME}
COMMAND ${TEST_NAME}
COMMENT "Running ${TEST_NAME}...")
In first six lines we create a project and create executable by using source file PrimeChecherTests.cpp
containing our test code. What is important to notice we do not need to put files of PrimeCheckerLib
to our test executable source files. We just link that library to our test application in line 9.
The first part of the task “Run test in compile time” starts in line 15. What do that lines mean? We create custom target running command same as the name of our test. In the result when we will build that defined target our test will be executed. Now we are just one step before running the test during compile time! Let’s jump to main CMakeList.txt
.
CMakeLists.txt of our application
cmake_minimum_required(VERSION 3.5)
project(RunTestInCompileTime)
add_subdirectory(third-parties)
add_subdirectory(prime-checker)
# creating application
add_executable(Application main.cpp)
# linking to library that we would like to use
target_link_libraries(Application PrimeCheckerLib)
# before create "Application" build target "Run_PrimeCheckerTests"
add_dependencies(Application Run_PrimeCheckerTests)
In line 9 we create the application. In line 15 we add a dependency to that executable. What does it mean? Build system before building Application
will build all its dependencies:
PrimeCheckerLib
– obvious, we use its code in main.cpp file.Run_PrimeCheckerTests
– this target depends onPrimeCheckerTests
, which depeds on Google Test Framework.
As I said before, building Run_PrimeCheckerTests
means running test. In the result we achieve our purpose by adding two lines. Simple, isn’t it?
Is it a good idea to run tests in compile time?
In general: yes. That solution is the first feedback about performing changes by us. Our application will not build if the tests will fail. It prevent’s sending artifacts with regression to other team members and of course making CI red in case when we forget to run tests before pushing our changes to the repository.
When this solution can be annoying? In case when our unit tests don’t meet unit test requirements e.g.:
- execution of tests takes a lot of time,
- tests are unstable,
- when test depends on other modules, e.g. hard drive.
We highly recommend to set up that build system feature at an early stage of the project. It really helps to avoid many problems during development. I hope it will help you too. Greetings!