C++
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
233: Code Coverage Tools for C++
How do I actually see which lines of my C++ code aren't being hit by tests?
If you're using GCC or Clang, you don't need to buy an expensive enterprise suite to get started. The industry standard for open-source C++ is gcov, usually paired with lcov to turn the raw data into something a human can actually read (like an HTML report).
To make this work, you have to tell the compiler to "instrument" the code. This means the compiler inserts little counters into every basic block of your program. When the program runs, it increments these counters. When it exits, it dumps those counts into .gcda files.
Let's say you have a PaymentProcessor class. You've written a few tests, but you're not sure if you've covered the "Insufficient Funds" logic:
bool PaymentProcessor::process(double amount) {
if (amount <= 0) {
return false; // Edge case 1
}
if (amount > balance) {
return false; // Edge case 2: Insufficient funds
}
balance -= amount;
return true;
}
To track this, you'd compile with these two flags: -fprofile-arcs -ftest-coverage. Then, run your test binary. Once the tests finish, you'll see .gcda files in your build folder. I usually run lcov to aggregate those and genhtml to create a website. When you open that site, the lines that were never executed are highlighted in red. It's a bit of a "eureka" moment when you realize you've been ignoring an entire else block for three weeks.
Why does my report say 90% coverage, but I'm still hitting bugs in the logic?
This is where a lot of developers get tripped up. They confuse Line Coverage with Branch Coverage. Line coverage just tells you if the execution pointer touched that line. Branch coverage tells you if every possible path through a conditional was taken.
Look at this line:
if (isValid && isAuthorized) {
doTransaction();
}
If your test case has both isValid and isAuthorized as true, the line is marked as "covered." But you've completely missed the cases where isValid is false, or where isValid is true but isAuthorized is false. You've hit the line, but you haven't tested the logic of the branch.
When you're configuring gcov, I highly recommend enabling branch probabilities. It'll show you exactly which "edge" of the decision diamond wasn't traversed. If you only chase line coverage, you're basically just painting the fence without checking if the fence is actually standing.
Is 100% coverage actually a realistic or healthy goal?
In my experience? Absolutely not. Chasing 100% is often a waste of engineering time and can actually lead to worse tests. I've seen developers write "junk tests" just to satisfy a coverage KPI—tests that call a function but don't actually assert anything meaningful about the result.
Think about your getters and setters. Do you really need a test to prove that return m_balance; returns the balance? No. That's just testing the compiler. I usually tell my teams to ignore trivial accessors and focus on the "critical paths"—the complex business logic, the state machine transitions, and the error handling code.
A healthy project usually lands somewhere between 70% and 90%. If you're at 100%, you're probably over-testing the trivial stuff. If you're at 30%, you're playing Russian Roulette with your production deployments. Find the sweet spot where your most complex functions are fully exercised, and stop stressing about the boilerplate.
📋 Practical Task
Exercise: Mapping Gaps in the PaymentProcessor Logic
You are provided with a PaymentProcessor class that handles three types of payments: CREDIT_CARD, PAYPAL, and CRYPTO. Each has its own validation logic (e.g., Crypto requires a wallet address check, Credit Card requires an expiry check).
Your Goal:
- Compile the provided source code using the
-fprofile-arcs -ftest-coverageflags. - Run the existing test suite (which only tests successful Credit Card payments).
- Use
lcovandgenhtmlto generate a coverage report. - Identify the specific branches in the
processPaymentswitch statement that are marked red (uncovered). - Write three new test cases specifically targeting those uncovered branches (e.g., a failed PayPal authentication and a missing Crypto wallet address).
- Re-run the coverage report and verify that the "Insufficient Funds" and "Invalid Wallet" branches are now green.
There are no comments for now.