# How Code Review Graph Detects Test Gaps: A Technical Deep Dive

> Discover how Code Review Graph identifies test gaps by analyzing missing TESTED_BY edges between production functions and test cases. Boost your code quality with this technical deep dive.

- Repository: [Tirth Kanani/code-review-graph](https://github.com/tirth8205/code-review-graph)
- Tags: deep-dive
- Published: 2026-07-27

---

**Code Review Graph detects test gaps by checking for the absence of `TESTED_BY` edges linking changed production functions to test cases in the repository graph.**

Code Review Graph (CRG) is an open-source tool that automates code review analysis by building a graph representation of your codebase. When analyzing pull requests, the system identifies untested changes by examining the relationships between production code and test cases stored in the graph structure.

## The Core Detection Algorithm

The test gap detection logic operates during the change analysis phase after parsing a PR diff. CRG builds a list of changed functions and classes, then validates whether each production node maintains a testing relationship in the graph.

### Filtering Production Code

The algorithm first isolates production code by skipping nodes marked as tests. Any node where `node.is_test` evaluates to `True` is excluded from gap detection, ensuring the analysis focuses only on implementation code that requires test coverage.

### Checking for TESTED_BY Relationships

For each production function in the changed set, CRG queries the graph store for outgoing edges. The system calls `store.get_edges_by_source(node.qualified_name)` to retrieve all relationships originating from the function. If none of these edges have `kind == "TESTED_BY"`, the function is flagged as untested and added to the `test_gaps` collection.

## Implementation in [`code_review_graph/changes.py`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/changes.py)

The core detection logic resides in the `analyze_changes()` function within [`code_review_graph/changes.py`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/changes.py) (lines 68-78). This routine iterates through changed functions and validates their testing relationships:

```python

# Detect test gaps: changed functions without TESTED_BY edges.

test_gaps: list[dict[str, Any]] = []
for node in changed_funcs:
    if node.is_test:
        continue
    # TESTED_BY edges are stored as source=production, target=test by the parser

    tested = store.get_edges_by_source(node.qualified_name)
    if not any(e.kind == "TESTED_BY" for e in tested):
        test_gaps.append({
            "name": _sanitize_name(node.name),
            "qualified_name": _sanitize_name(node.qualified_name),
            "file": node.file_path,
            "line_start": node.line_start,
            "line_end": node.line_end,
        })

```

The `TESTED_BY` edges are stored with the production code as the source and the test case as the target, allowing the system to trace which tests verify specific implementation functions.

## Processing and Reporting Test Gaps

After collecting untested functions, CRG deduplicates entries for human-readable summaries. The processing logic (lines 97-115 in [`changes.py`](https://github.com/tirth8205/code-review-graph/blob/main/changes.py)) limits the display to five unique function names to prevent overwhelming reviewers:

```python
if test_gaps:
    # Dedup by bare name in the human summary.

    seen_names: set[str] = set()
    gap_names: list[str] = []
    for g in test_gaps:
        n = g["name"]
        if n in seen_names:
            continue
        seen_names.add(n)
        gap_names.append(n)
        if len(gap_names) >= 5:
            break
    summary_parts.append(f"  - Untested: {', '.join(gap_names)}")

```

This deduplication ensures that overloaded functions or multiple changes to the same function appear only once in the review summary.

## Verification Through Unit Tests

The detection mechanism is validated in [`tests/test_changes.py`](https://github.com/tirth8205/code-review-graph/blob/main/tests/test_changes.py) (lines 70-88), which creates functions with and without `TESTED_BY` edges to verify accurate gap identification:

```python
def test_analyze_detects_test_gaps(self):
    """Changed functions without TESTED_BY edges are flagged as test gaps."""
    self._add_func("untested_a", path="app.py", line_start=1, line_end=10)
    self._add_func("untested_b", path="app.py", line_start=15, line_end=25)
    self._add_func("tested_c", path="app.py", line_start=30, line_end=40)

    # Only tested_c has a test.

    self._add_func("test_c", path="test_app.py", is_test=True)
    self._add_tested_by("app.py::tested_c", "test_app.py::test_c", "test_app.py")

    result = analyze_changes(
        self.store,
        changed_files=["app.py"],
        changed_ranges={"app.py": [(1, 40)]},
    )
    gap_names = {g["name"] for g in result["test_gaps"]}
    assert "untested_a" in gap_names
    assert "untested_b" in gap_names
    assert "tested_c" not in gap_names

```

This test confirms that only functions lacking `TESTED_BY` relationships appear in the `test_gaps` list, while properly tested code is excluded from the results.

## Practical Usage Examples

### Detecting Gaps in a Pull Request

To programmatically detect test gaps for a specific set of changes, import `analyze_changes` and provide the changed file paths and line ranges extracted from the diff:

```python
from code_review_graph.changes import analyze_changes

# Assume `store` is an initialized GraphStore with the repository graph loaded.

result = analyze_changes(
    store,
    changed_files=["src/app.py"],
    changed_ranges={"src/app.py": [(1, 120)]},  # line ranges from the diff

)

print("Test gaps found:")
for gap in result["test_gaps"]:
    print(f"- {gap['name']} ({gap['file']}:{gap['line_start']}-{gap['line_end']})")

```

### Generating PR Comments

The [`scripts/render_pr_comment.py`](https://github.com/tirth8205/code-review-graph/blob/main/scripts/render_pr_comment.py) utility automates the creation of human-friendly PR comments that include test gap summaries:

```bash
python -m code_review_graph.scripts.render_pr_comment \
    --repo-path /path/to/repo \
    --base main \
    --head feature-branch

```

This script internally invokes `analyze_changes`, extracts the `test_gaps` list, and formats output such as:

```

🔍 2 test gap(s) – Untested: handle_user_input, save_to_db

```

### Custom Filtering by Code Size

You can extend the basic detection to focus on significant untested changes by filtering gaps based on function size:

```python
large_gaps = [
    g for g in result["test_gaps"]
    if (g["line_end"] - g["line_start"]) > 20
]

for g in large_gaps:
    print(f"{g['name']} spans {g['line_end'] - g['line_start'] + 1} lines")

```

This approach helps prioritize testing efforts for substantial untested logic blocks.

## Summary

- **Test gaps are defined by missing `TESTED_BY` edges** in the code graph between production functions and test cases.
- **Detection occurs in [`code_review_graph/changes.py`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/changes.py)** through the `analyze_changes()` function, which filters test nodes and validates edge relationships.
- **The algorithm skips test code** by checking the `node.is_test` property before evaluating coverage.
- **Results are deduplicated** to five unique names for readable PR summaries while preserving full gap data in the report.
- **Unit tests in [`tests/test_changes.py`](https://github.com/tirth8205/code-review-graph/blob/main/tests/test_changes.py)** verify that only untested functions appear in gap detection results.

## Frequently Asked Questions

### What constitutes a test gap in Code Review Graph?

A test gap is any changed production function or class that lacks a `TESTED_BY` edge connecting it to a test case in the repository graph. According to the source code in [`code_review_graph/changes.py`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/changes.py), if `store.get_edges_by_source(node.qualified_name)` returns no edges with `kind == "TESTED_BY"`, the node is classified as untested and added to the `test_gaps` list.

### How does Code Review Graph distinguish between test and production code?

CRG uses the `is_test` boolean property on graph nodes to differentiate test files from production code. During gap detection, the algorithm explicitly checks `if node.is_test: continue` to skip nodes that represent test functions, ensuring analysis focuses only on implementation code that requires coverage validation.

### Can Code Review Graph detect test gaps across multiple files?

Yes, the `analyze_changes()` function accepts a list of `changed_files` and a dictionary of `changed_ranges` mapping file paths to line number tuples. The system processes all provided files in a single analysis pass, collecting test gaps from across the entire changeset rather than examining files in isolation.

### Where is the test gap detection logic located in the source code?

The primary detection logic resides in [`code_review_graph/changes.py`](https://github.com/tirth8205/code-review-graph/blob/main/code_review_graph/changes.py) between lines 68-78, within the `analyze_changes()` function. Additional related functionality, including the deduplication logic for gap names, appears at lines 97-115 in the same file. The test suite validating this behavior is located in [`tests/test_changes.py`](https://github.com/tirth8205/code-review-graph/blob/main/tests/test_changes.py) at lines 70-88.