COMPLETE C UNIT TESTING SETUP USING GTEST ON LINUX
Project structure
Module example (PCM)
Google Test setup for C
Test runner
Build system (CMake)
Report generation
Automation with scripts
Sample output
1. PROJECT STRUCTURE
firmware_project/
├── CMakeLists.txt
├── src/
│ ├── pcm/
│ │ ├── pcm.c
│ │ └── pcm.h
│ ├── tcm/
│ │ └── ...
│ └── fcm/
│ └── ...
├── tests/
│ ├── CMakeLists.txt
│ ├── main.cpp # GTest runner
│ ├── pcm/
│ │ └── test_pcm.cpp
│ └── ...
└── scripts/
└── run_tests.sh # Automation script
2. EXAMPLE MODULE (PCM)
src/pcm/pcm.h
#ifndef PCM_H
#define PCM_H
int pcm_add(int a, int b);
#endif
src/pcm/pcm.c
#include "pcm.h"
int pcm_add(int a, int b) {
return a + b;
}
3. WRITING C TESTS USING GTEST
GTest is C++-only, so to test C functions, you need to extern "C" them.
tests/pcm/test_pcm.cpp
#include <gtest/gtest.h>
extern "C" {
#include "../../src/pcm/pcm.h"
}
TEST(PCMTests, AddFunction) {
EXPECT_EQ(pcm_add(2, 3), 5);
}
4. GTEST TEST RUNNER
tests/main.cpp
#include <gtest/gtest.h>
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
5. CMAKE CONFIGURATION
CMakeLists.txt (Root)
cmake_minimum_required(VERSION 3.10)
project(FirmwareTest C)
set(CMAKE_C_STANDARD 11)
# Add subdirectories
add_subdirectory(src)
add_subdirectory(tests)
src/CMakeLists.txt
add_library(pcm pcm/pcm.c)
# You can do the same for tcm.c and fcm.c if needed
tests/CMakeLists.txt
enable_testing()
find_package(GTest REQUIRED)
include_directories(${GTEST_INCLUDE_DIRS})
include_directories(${PROJECT_SOURCE_DIR}/src)
add_executable(runTests
main.cpp
pcm/test_pcm.cpp
)
target_link_libraries(runTests
GTest::GTest
GTest::Main
pcm
)
add_test(NAME FirmwareTest COMMAND runTests)
6. GENERATING TEST REPORTS
You can use --gtest_output=xml to export test results.
./runTests --gtest_output=xml:test_results.xml
To convert to HTML or PDF, use JUnit-style test tools like:
junit-viewer (HTML)
xsltproc + junit-xml.xsl
pytest or CI tool support (like GitLab/GitHub Actions)
Or use a third-party tool like:
npm install -g junit-viewer
junit-viewer --results=test_results.xml --save report.html
7. AUTOMATION SCRIPT
scripts/run_tests.sh
#!/bin/bash
set -e
BUILD_DIR=build
# Create build directory
mkdir -p $BUILD_DIR
cd $BUILD_DIR
# Build project
cmake ..
make
# Run tests and generate report
./runTests --gtest_output=xml:test_results.xml
# Convert to HTML
junit-viewer --results=test_results.xml --save test_report.html
echo "Tests completed. Report: $BUILD_DIR/test_report.html"
Make it executable:
chmod +x scripts/run_tests.sh
8. RUNNING EVERYTHING
./scripts/run_tests.sh
TO EXTEND TO TCM / FCM MODULES
Add tcm/test_tcm.cpp, fcm/test_fcm.cpp
Include their respective headers via extern "C"
Add files to CMakeLists.txt
TOOLS NEEDED ON LINUX
sudo apt update
sudo apt install cmake g++ libgtest-dev
sudo apt install npm
sudo npm install -g junit-viewer
For PDF export from HTML:
sudo apt install wkhtmltopdf
wkhtmltopdf test_report.html test_report.pdf
Add Code Coverage Support to Your C + GTest Project
GCC's gcov for code coverage
lcov to collect coverage data
genhtml to generate a human-readable HTML report
1. Install Required Tools
sudo apt update
sudo apt install lcov gcovr
Optional (for HTML rendering):
sudo apt install genhtml
2. Update CMake to Enable Coverage
Modify both your root and test CMakeLists.txt files.
🛠 Root CMakeLists.txt
Add a coverage flag:
option(CODE_COVERAGE "Enable coverage reporting" ON)
if(CODE_COVERAGE)
message(STATUS "Code coverage enabled")
add_compile_options(-g -O0 --coverage)
link_libraries(gcov)
endif()
tests/CMakeLists.txt
Make sure --coverage is added to both compile and link flags:
if(CODE_COVERAGE)
target_compile_options(runTests PRIVATE --coverage)
target_link_libraries(runTests --coverage)
endif()
3. Clean + Build + Run Tests
rm -rf build
mkdir build && cd build
cmake -DCODE_COVERAGE=ON ..
make
./runTests
4. Generate Coverage Report
Use lcov and genhtml:
# Capture coverage
lcov --capture --directory . --output-file coverage.info
# Remove external/lib paths
lcov --remove coverage.info '/usr/*' '*/tests/*' --output-file
coverage.info.cleaned
# Generate HTML report
genhtml coverage.info.cleaned --output-directory coverage_html
# Open the report
xdg-open coverage_html/index.html
5. Automate Everything with Script
scripts/run_tests.sh
#!/bin/bash
set -e
BUILD_DIR=build
# Clean build
rm -rf $BUILD_DIR
mkdir -p $BUILD_DIR
cd $BUILD_DIR
# Build with coverage
cmake -DCODE_COVERAGE=ON ..
make
# Run tests
./runTests --gtest_output=xml:test_results.xml
# Generate test report (HTML)
junit-viewer --results=test_results.xml --save test_report.html
# Generate code coverage
lcov --capture --directory . --output-file coverage.info
lcov --remove coverage.info '/usr/*' '*/tests/*' --output-file
coverage_filtered.info
genhtml coverage_filtered.info --output-directory coverage_html
echo "Test and coverage reports generated:"
echo " - HTML Test Report: $(pwd)/test_report.html"
echo " - HTML Coverage Report: $(pwd)/coverage_html/index.html"
6. Directory Layout Recap
firmware_project/
├── src/
│ ├── pcm/
│ └── tcm/
│ └── fcm/
├── tests/
│ └── pcm/test_pcm.cpp
│ └── ...
├── build/
│ └── test_results.xml
│ └── coverage_html/
├── scripts/
│ └── run_tests.sh
├── CMakeLists.txt
Bonus: PDF Coverage Report (Optional)
To convert the HTML report into a PDF:
wkhtmltopdf coverage_html/index.html coverage_report.pdf
7. Final Command
To run everything:
./scripts/run_tests.sh