Branch Coverage
A code coverage metric that measures whether each branch of every conditional statement (if/else, switch) has been executed during testing.
What Is Branch Coverage?
Branch coverage is a code coverage metric that measures whether each possible branch of every decision point in the code has been executed during testing. A decision point is any place where the program can take more than one path: if/else statements, switch/case blocks, ternary operators, logical short-circuit expressions, and loop entry/exit conditions.
Branch coverage is more rigorous than line coverage. A single if-else statement on one line can have two branches (true and false), but line coverage only asks whether that line was executed — it cannot distinguish which branch was taken. A test that always evaluates the condition as true would show the line as covered, but the false branch remains untested. Branch coverage catches this gap.
Consider this example: const fee = amount > 1000 ? 25 : 10. A test that only passes amount = 2000 would achieve 100% line coverage (the line was executed) but only 50% branch coverage (only the true branch was taken). This distinction matters because the untested branch could contain a bug that line coverage would never reveal.
Branch coverage is sometimes called “decision coverage,” though some formal definitions treat them as slightly different. In practice, most tools and teams use the terms interchangeably. It is one of the metrics reported by standard coverage tools like Istanbul/nyc (JavaScript), JaCoCo (Java), and coverage.py (Python).
How It Works
Coverage tools track branch coverage by instrumenting each decision point to record which branch was taken during each execution. After the test suite runs, the tool calculates the percentage of branches that were exercised.
Here is a concrete example in JavaScript:
// shipping.js
function calculateShipping(weight, country, isPrime) {
if (weight <= 0) {
throw new Error("Weight must be positive");
}
let cost;
if (country === "US") {
cost = weight * 0.5;
} else {
cost = weight * 2.0;
}
if (isPrime) {
cost = 0;
}
return cost;
}
This function has three decision points (three if statements), each with two branches, giving six total branches. A test suite that only tests the happy path:
test("calculates US shipping", () => {
expect(calculateShipping(10, "US", false)).toBe(5);
});
This test covers three of six branches (weight is positive, country is US, isPrime is false). Branch coverage: 50%. The untested branches are:
weight <= 0(the error path)country !== "US"(the international path)isPrime === true(the free shipping path)
Adding tests for those paths:
test("throws on zero weight", () => {
expect(() => calculateShipping(0, "US", false)).toThrow();
});
test("charges more for international shipping", () => {
expect(calculateShipping(10, "UK", false)).toBe(20);
});
test("prime members get free shipping", () => {
expect(calculateShipping(10, "US", true)).toBe(0);
});
Now all six branches are covered: 100% branch coverage.
In Python, the coverage tool with the --branch flag reports branch coverage:
coverage run --branch -m pytest
coverage report -m
Name Stmts Miss Branch BrPart Cover Missing
-----------------------------------------------------------
shipping.py 12 0 6 0 100%
The BrPart column shows partially covered branches — decision points where only one of the two paths was executed. This is the most actionable column in the report because it pinpoints exactly where tests need to be added.
Why It Matters
Branch coverage reveals testing gaps that line coverage hides. This is not a theoretical concern — it is a practical reality that affects real codebases daily. Many of the bugs that reach production live in untested branches: error handling paths, fallback logic, boundary conditions, and feature flag variations.
Consider a function with a try/catch block. If tests only exercise the happy path (the try succeeds), line coverage might show the catch block as uncovered, but a short-circuit expression like user?.permissions?.includes("admin") could achieve full line coverage while only testing the path where user and permissions are defined. Branch coverage would flag that the nullish paths — when user is null or permissions is undefined — were never tested.
Branch coverage is also essential for safety-critical and security-sensitive code. Regulatory frameworks like DO-178C (aviation software) and IEC 62304 (medical devices) explicitly require branch coverage — not just line coverage — as a certification requirement. Even outside regulated industries, branch coverage provides a meaningful signal about test thoroughness that line coverage alone cannot deliver.
Best Practices
- Track branch coverage alongside line coverage. Do not rely on line coverage alone. A module with 95% line coverage and 60% branch coverage has significant untested logic paths. Many CI tools can report both metrics side by side.
- Focus branch coverage on conditional logic. Complex functions with many
if/elsebranches,switchstatements, and early returns benefit the most from branch coverage analysis. Simple functions with no branching do not need this level of scrutiny. - Use branch coverage to prioritize test writing. When deciding where to invest testing effort, look at the
BrPart(partially covered branches) column in coverage reports. These are the exact decision points that need additional test cases. - Set branch coverage thresholds separately. Branch coverage percentages are typically 10-20% lower than line coverage for the same codebase. Set a realistic branch coverage threshold (e.g., 65-75%) rather than applying the same number as your line coverage requirement.
- Review branch coverage during pull requests. New code with conditional logic should have tests that exercise both the true and false paths. Reviewers should check the coverage report to confirm this.
Common Mistakes
- Ignoring branch coverage entirely. Many teams track only line coverage and assume high line coverage means thorough testing. This creates blind spots in error handling, edge cases, and conditional logic that branch coverage would reveal.
- Testing only one side of a conditional. A common pattern is testing the happy path (the
ifbranch) and skipping the error path (theelsebranch). Branch coverage makes this gap quantifiable and visible. - Confusing branch coverage with path coverage. Branch coverage measures whether each individual branch was taken at least once. Path coverage measures whether every possible combination of branches was tested. For a function with three independent
ifstatements, branch coverage requires 6 test cases (2 per branch), but path coverage requires 8 (2^3 combinations). Path coverage is often infeasible for complex code; branch coverage is a practical and effective alternative. - Over-optimizing for the percentage. Writing contrived tests solely to cover a branch (like testing that a catch block exists) without asserting on meaningful behavior yields coverage without confidence. The goal is not to cover the branch but to verify that the branch behaves correctly.
Related Terms
Learn More
Free Newsletter
Stay ahead with AI dev tools
Weekly insights on AI code review, static analysis, and developer productivity. No spam, unsubscribe anytime.
Join developers getting weekly AI tool insights.
Axolo
Codacy
Codara
CodeScene