[2026] C++ Code Coverage Complete Guide | gcov, lcov, Codecov Practical Usage
이 글의 핵심
From C++ code coverage measurement to CI/CD integration. gcov, lcov, Codecov tool comparison, line/branch/function coverage analysis, test quality improvement strategy. Build practical workflow integrated with Google Test.
Introduction
“I wrote tests, but how do I know if they’re sufficient?” Code Coverage provides a quantitative answer to this question. It’s a powerful tool that measures the proportion of code actually executed by tests, finding untested areas. Unit testing is important in all languages. Python’s pytest·CI, Node.js Jest, C++ Google Test, Go’s go test, Rust’s cargo test are close to standards in their respective ecosystems. Putting coverage gates in CI is covered together in C++ GitHub Actions Multi-OS Build and Node.js GitHub Actions CI/CD.
Problem Scenario
int divide(int a, int b) {
if (b == 0) { // Line 1: Condition check
return 0; // Line 2: Error handling (not executed!)
}
return a / b; // Line 3: Normal path
}
// Test code
TEST(DivideTest, NormalCase) {
EXPECT_EQ(divide(10, 2), 5); // Only test normal case
}
// Coverage measurement result:
// - Line 1: ✅ Executed (condition check)
// - Line 2: ❌ Not executed (error handling missing!)
// - Line 3: ✅ Executed (normal path)
// Line coverage: 66% (2/3)
Problem: Error case of dividing by zero is not tested! This guide covers everything from types of code coverage to measurement tools, CI/CD integration, and practical workflows.
Reality in Production
When learning development, everything is clean and theoretical. But production is different. You wrestle with legacy code, chase tight deadlines, and face unexpected bugs. The content covered in this guide was initially learned as theory, but I realized “ah, that’s why it’s designed this way” while applying it to actual projects. What stands out in my memory is the trial and error from my first project. I did it as I learned from books but spent days not knowing why it didn’t work. Eventually, I found the problem through a senior developer’s code review and learned a lot in the process. This guide covers not only theory but also pitfalls you may encounter in practice and their solutions.
Table of Contents
- Code Coverage Basic Concepts
- Coverage Types
- gcov Usage
- Visualization with lcov
- Google Test Integration
- Practical Workflow
- CI/CD Integration
- Tool Comparison
- Common Issues
- Best Practices Below is an implementation example using mermaid. Understand the role of each part while examining the code.
flowchart LR
A[Source Code] --> B[Compile
--coverage]
B --> C[Run Tests]
C --> D[".gcda files
generated"]
D --> E[gcov analysis]
E --> F[Coverage
Report]
D --> G[lcov collect]
G --> H[HTML
Report]
style F fill:#51cf66
style H fill:#4dabf7
Code Coverage Basic Concepts
Code coverage measures the proportion of code executed by tests.
gcov Usage (GCC Built-in Tool)
gcov is a code coverage analysis tool built into GCC.
Basic Workflow
Here is detailed implementation code using bash. Understand the role of each part while examining the code.
# 1. Compile with coverage option
g++ --coverage program.cpp -o program
# Or
g++ -fprofile-arcs -ftest-coverage program.cpp -o program
# 2. Run program (run tests)
./program
# 3. Check coverage data files generated
ls *.gcda *.gcno
# program.gcno: Generated at compile time (graph info)
# program.gcda: Generated at runtime (execution count)
# 4. Analyze with gcov
gcov program.cpp
# 5. Check result file
cat program.cpp.gcov
Note: Using with optimization (-O2 or higher) can misalign line mapping, so often have a dedicated build type for coverage.
Practical Example: Calculator Program
Here is detailed implementation code using C++. Import the necessary modules and perform branching with conditionals. Understand the role of each part while examining the code.
// calculator.cpp
#include <iostream>
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
int multiply(int a, int b) {
return a * b;
}
int divide(int a, int b) {
if (b == 0) {
std::cerr << "Error: Division by zero\n";
return 0;
}
return a / b;
}
int main() {
std::cout << "10 + 5 = " << add(10, 5) << "\n";
std::cout << "10 - 5 = " << subtract(10, 5) << "\n";
std::cout << "10 * 5 = " << multiply(10, 5) << "\n";
// divide function not called!
return 0;
}
Here is detailed implementation code using bash. Perform branching with conditionals. Understand the role of each part while examining the code.
# Compile and run
$ g++ --coverage calculator.cpp -o calculator
$ ./calculator
10 + 5 = 15
10 - 5 = 5
10 * 5 = 50
# Coverage analysis
$ gcov calculator.cpp
File 'calculator.cpp'
Lines executed:73.33% of 15
Creating 'calculator.cpp.gcov'
# Check detailed report
$ cat calculator.cpp.gcov
-: 0:Source:calculator.cpp
-: 1:#include <iostream>
-: 2:
1: 3:int add(int a, int b) {
1: 4: return a + b;
-: 5:}
-: 6:
1: 7:int subtract(int a, int b) {
1: 8: return a - b;
-: 9:}
-: 10:
1: 11:int multiply(int a, int b) {
1: 12: return a * b;
-: 13:}
-: 14:
#####: 15:int divide(int a, int b) { // ##### = not executed!
#####: 16: if (b == 0) {
#####: 17: std::cerr << "Error: Division by zero\n";
#####: 18: return 0;
-: 19: }
#####: 20: return a / b;
-: 21:}
-: 22:
1: 23:int main() {
1: 24: std::cout << "10 + 5 = " << add(10, 5) << "\n";
1: 25: std::cout << "10 - 5 = " << subtract(10, 5) << "\n";
1: 26: std::cout << "10 * 5 = " << multiply(10, 5) << "\n";
1: 27: return 0;
-: 28:}
# Interpretation:
# - "1:": Executed once
# - "#####:": Not executed (uncovered)
# - "-:": Non-executable line (comments, declarations, etc.)
gcov Advanced Options
# Include branch coverage
gcov -b calculator.cpp
# Output:
# Function 'divide'
# Lines executed:0.00% of 5
# Branches executed:0.00% of 2
# Taken at least once:0.00% of 2
# Function-level coverage
gcov -f calculator.cpp
# Output:
# Function 'add'
# Lines executed:100.00% of 1
#
# Function 'subtract'
# Lines executed:100.00% of 1
#
# Function 'multiply'
# Lines executed:100.00% of 1
#
# Function 'divide'
# Lines executed:0.00% of 5
# Show only unexecuted lines
gcov -u calculator.cpp
# All options combined
gcov -b -f -u calculator.cpp
CMake Integration
Here is detailed implementation code using cmake. Perform branching with conditionals. Understand the role of each part while examining the code.
# CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(MyProject)
set(CMAKE_CXX_STANDARD 17)
# Add coverage build type
if(CMAKE_BUILD_TYPE STREQUAL "Coverage")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage")
endif()
add_executable(myapp src/main.cpp src/math.cpp)
# Add coverage target
if(CMAKE_BUILD_TYPE STREQUAL "Coverage")
add_custom_target(coverage
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/myapp
COMMAND gcov ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMENT "Generating coverage report"
)
endif()
Below is an implementation example using bash. Try running the code directly to check its operation.
# Build and generate coverage
mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Coverage ..
make
make coverage
Visualization with lcov (HTML Report)
lcov is a tool that collects gcov data and generates beautiful HTML reports.
Installation
Below is an implementation example using bash. Try running the code directly to check its operation.
# Ubuntu/Debian
sudo apt install lcov
# macOS
brew install lcov
# Arch Linux
sudo pacman -S lcov
Basic Usage
Here is detailed implementation code using bash. Understand the role of each part while examining the code.
# 1. Compile with coverage option
g++ --coverage src/*.cpp -o myapp -lgtest -lgtest_main
# 2. Run tests
./myapp
# 3. Collect coverage data
lcov --capture --directory . --output-file coverage.info
# 4. Generate HTML report
genhtml coverage.info --output-directory coverage_html
# 5. View in browser
open coverage_html/index.html # macOS
xdg-open coverage_html/index.html # Linux
start coverage_html/index.html # Windows (Git Bash)
Google Test Integration
Complete example using Google Test with coverage.
CMakeLists.txt
Here is detailed implementation code using cmake. Perform branching with conditionals. Understand the role of each part while examining the code.
cmake_minimum_required(VERSION 3.15)
project(StringUtilsProject)
set(CMAKE_CXX_STANDARD 17)
# Download Google Test
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.14.0
)
FetchContent_MakeAvailable(googletest)
# Source library
add_library(string_utils src/string_utils.cpp)
target_include_directories(string_utils PUBLIC src)
# Test executable
add_executable(string_utils_test test/string_utils_test.cpp)
target_link_libraries(string_utils_test string_utils gtest gtest_main)
# Coverage settings
option(ENABLE_COVERAGE "Enable coverage reporting" OFF)
if(ENABLE_COVERAGE)
target_compile_options(string_utils PRIVATE --coverage)
target_link_options(string_utils PRIVATE --coverage)
target_compile_options(string_utils_test PRIVATE --coverage)
target_link_options(string_utils_test PRIVATE --coverage)
# Coverage target
find_program(LCOV lcov REQUIRED)
find_program(GENHTML genhtml REQUIRED)
add_custom_target(coverage
COMMAND ${LCOV} --directory . --zerocounters
COMMAND $<TARGET_FILE:string_utils_test>
COMMAND ${LCOV} --capture --directory . --output-file coverage.info
COMMAND ${LCOV} --remove coverage.info '/usr/*' '*/test/*' '*/googletest/*'
--output-file coverage_filtered.info
COMMAND ${GENHTML} coverage_filtered.info --output-directory coverage_html
COMMAND ${LCOV} --summary coverage_filtered.info
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
COMMENT "Generating coverage report"
DEPENDS string_utils_test
)
endif()
# CTest integration
enable_testing()
add_test(NAME string_utils_test COMMAND string_utils_test)
Build and Run
# Build with coverage enabled
mkdir build && cd build
cmake -DENABLE_COVERAGE=ON ..
make
# Run tests
./string_utils_test
# Output:
# [==========] Running 4 tests from 1 test suite.
# [----------] Global test environment set-up.
# [----------] 4 tests from StringUtilsTest
# [ RUN ] StringUtilsTest.TrimSpaces
# [ OK ] StringUtilsTest.TrimSpaces (0 ms)
# [ RUN ] StringUtilsTest.SplitString
# [ OK ] StringUtilsTest.SplitString (0 ms)
# [ RUN ] StringUtilsTest.StartsWith
# [ OK ] StringUtilsTest.StartsWith (0 ms)
# [ RUN ] StringUtilsTest.EndsWith
# [ OK ] StringUtilsTest.EndsWith (0 ms)
# [----------] 4 tests from StringUtilsTest (0 ms total)
# [==========] 4 tests from 1 test suite ran. (0 ms total)
# [ PASSED ] 4 tests.
# Generate coverage report
make coverage
# Output:
# Generating coverage report
# Overall coverage rate:
# lines......: 100.0% (24 of 24 lines)
# functions..: 100.0% (4 of 4 functions)
# View HTML report
open coverage_html/index.html
Practical Workflow
Daily Development Workflow
Here is detailed implementation code using bash. Understand the role of each part while examining the code.
# 1. Develop new feature
vim src/new_feature.cpp
# 2. Write tests
vim test/new_feature_test.cpp
# 3. Build and test
mkdir -p build && cd build
cmake -DENABLE_COVERAGE=ON ..
make
./test_runner
# 4. Check coverage
make coverage
# 5. Check uncovered parts
open coverage_html/index.html
# 6. Write additional tests (uncovered parts)
vim test/new_feature_test.cpp
# 7. Test and check coverage again
make && make coverage
# 8. Commit when goal achieved
git add .
git commit -m "Add new feature with 90% coverage"
Summary
Key Points
- Code Coverage: Measures proportion of code executed by tests
- gcov: GCC’s built-in coverage tool
- lcov: Frontend tool generating HTML reports
- Google Test: C++ testing framework
- CI/CD: Automate coverage measurement and reporting
Best Practices
- Set realistic coverage targets (70-90%)
- Focus on critical business logic
- Don’t chase 100% coverage blindly
- Use coverage as a guide, not a goal
- Integrate into CI/CD pipeline
- Review coverage reports regularly