[2026] C++ Code Coverage Complete Guide | gcov, lcov, Codecov Practical Usage

[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

  1. Code Coverage Basic Concepts
  2. Coverage Types
  3. gcov Usage
  4. Visualization with lcov
  5. Google Test Integration
  6. Practical Workflow
  7. CI/CD Integration
  8. Tool Comparison
  9. Common Issues
  10. 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

  1. Code Coverage: Measures proportion of code executed by tests
  2. gcov: GCC’s built-in coverage tool
  3. lcov: Frontend tool generating HTML reports
  4. Google Test: C++ testing framework
  5. 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

Next Steps


... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3