Skip to Content
Course content

233: Code Coverage Tools for C++

Click on the "Edit" button in the top corner of the screen to edit your slide content.

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:

  1. Compile the provided source code using the -fprofile-arcs -ftest-coverage flags.
  2. Run the existing test suite (which only tests successful Credit Card payments).
  3. Use lcov and genhtml to generate a coverage report.
  4. Identify the specific branches in the processPayment switch statement that are marked red (uncovered).
  5. Write three new test cases specifically targeting those uncovered branches (e.g., a failed PayPal authentication and a missing Crypto wallet address).
  6. Re-run the coverage report and verify that the "Insufficient Funds" and "Invalid Wallet" branches are now green.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.