C++ Code Coverage Complete Guide | gcov· lcov
이 글의 핵심
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\n--coverage]
B --> C[Run Tests]
C --> D[".gcda files\ngenerated"]
D --> E[gcov analysis]
E --> F[Coverage\nReport]
D --> G[lcov collect]
G --> H[HTML\nReport]
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
Next Steps
- C++ Google Test Guide
- C++ CMake Build System
- C++ CI/CD with GitHub Actions
Related Articles
자주 묻는 질문 (FAQ)
Q. 이 내용을 실무에서 언제 쓰나요?
A. From C++ code coverage measurement to CI/CD integration. gcov, lcov, Codecov tool comparison, line/branch/function cover… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.
Q. 선행으로 읽으면 좋은 글은?
A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.
Q. 더 깊이 공부하려면?
A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ Conan 완벽 가이드 | 현대적인 C++ 패키지 관리
- C++ CLion 완벽 설정 가이드 | CMake·디버거·코드 분석·vcpkg·원격 개발 [#53-1]
- C++ CMake | ‘빌드 시스템’ 초보자 가이드
- C++ Google Test | gtest 설치부터 TEST·EXPECT_EQ
- Node.js 테스트 | Jest, Mocha, Supertest 완벽 가이드
- Python 환경 설정 | Windows/Mac에서 Python 설치하고 시작하기
- [Go 2주 완성 #07] Day 12~13: 의존성 관리와 테스팅 - CMake보다 쉬운 세상
- Rust 테스팅 | 단위 테스트, 통합 테스트, 벤치마크
- C++ CI/CD 파이프라인: GitHub Actions를 이용한 멀티 OS 자동 빌드·테스트 가이드
- GitHub Actions로 Node.js CI/CD 파이프라인 만들기 | 테스트·빌드·Docker·배포
이 글에서 다루는 키워드 (관련 검색어)
C++, code-coverage, testing, gcov, lcov, quality, CI/CD 등으로 검색하시면 이 글이 도움이 됩니다.