Motivation, methodology, and extended results for Q&A.
From LLM Assistance to Agentic Delivery
Assisted era: the LLM never shipped anything
The developer owns the change. The model completes a line or a function inside it. The developer decides what to test, and signs the commit.
Agentic era: the agent ships the whole change
Branch, commits, PR description, review request, all produced end to end with no human in the loop until review time.
Merged #482
feat: apply discount rules at checkout
codex/apply-discount-rules merged into main
1 file changed +6−1
Authored by an autonomous coding agent · no human commits · approved and merged
Those 6 lines are now in production.
In Production, but Is It Properly Tested?
The diff inside PR #482. Every added line is either executed by some test, or it is not.
payments/discount.py (agent-authored PR)
def apply_discount(order, code):
- return order.total+ rule = RULES.get(code)
+ if rule is None:
+ raise UnknownCode(code)
+ if order.total < rule.min_total:
+ return order.total
+ return order.total - rule.amount6 new executable lines, including one error path.
Neither source has been measured in practice.
Only two things can execute them:
1
The project's existing tests
Written before this change, so no reason to reach a branch that did not exist yesterday.
2
Tests the agent adds itself
Only if it writes any, and only if they hit these lines, not just the code around them.
Related Work: SWE-bench Asks a Different Question
Agent ships a diff
the model's patch for the issue
▶
FAIL_TO_PASS
tests that failed before now pass
▶
PASS_TO_PASS
tests that passed before still pass
▶
Instance resolved
the patch counts as correct
Benchmarks reward a patch that makes tests pass, not a patch whose own lines are tested.
the same six added lines, under the issue's own tests
+ rule = RULES.get(code) ✓ executed+ if rule is None: ✓ executed+ raise UnknownCode(code) ✗ never executed+ if order.total < rule.min_total: ✓ executed+ return order.total ✗ never executed+ return order.total - rule.amount ✓ executedFAIL_TO_PASS is green. Two of the six lines were never run, including the error path.
What the verdict does not say
FAIL_TO_PASS checks that the issue's test now passes; one path through the patch is enough.
PASS_TO_PASS guards the rest of the suite from breakage; it says nothing about the new lines.
The Question We Ask Instead
RQ1
How often do agentic PRs contain test code changes?
Presence, not quality: classify every PR as Code + Tests, Code-Under-Test-Only, or Tests-Only.
RQ2
What is the test coverage of the code changes in agentic PRs?
RQ2a · Existing tests
How much of the agent's added code does the project's own suite already cover?
RQ2b · Agent-written tests
How much coverage do the agent's own tests add on top of the existing suite?
The 10 analyzed Java repos are smaller than the full set: 29K vs 102K LOC, 437 vs 745 stars, 116 vs 254 forks.
Comparable on commits (2,271 vs 3,983) and age (4,178 vs 3,785 days).
Java results may reflect testing practice in smaller projects.
Sampling bias: Python
The 34 analyzed Python repos show no substantial difference from the full set on any metric:
31K vs 25K LOC · 1,202 vs 872 commits · 1,065 vs 1,268 days · 676 vs 734 stars · 120 vs 141 forks.
Python results generalize to the wider Python sample.
Other limitations: coverage measured only on merged PRs from buildable, instrumentable repos; line coverage (not branch); test-file identification relies on path and naming conventions; AIDev's own agent-attribution limits are inherited.
Methodology for RQ1: Classifying Test Behaviour
AIDev ships no PR-level diffs, so every measurement starts from a patch we rebuild ourselves.
1
Rebuild the patch
Clone each repository and reconstruct the unified diff from the PR's base and head SHAs, using git diff <base>..<head>, giving one self-contained patch per PR.
2
Identify test files
By directory (test/, tests/) and by naming convention: *Test, *Tests, *TestCase, Test*, *IT, *ITCase in Java; test_*.py and *_test.py in Python.
3
Detect newly added tests
Test-method declarations at the head commit minus those at the base. Java: @Test, @ParameterizedTest, @RepeatedTest, @TestFactory, @TestTemplate, or JUnit 3 naming. Python: def / async def starting with test.
Every PR lands in exactly one bucket
Code + Tests
changes code under test and test files
Code-Under-Test-Only
changes code under test, no test file
Tests-only
changes test files alone
A second criterion splits Code + Tests further: a PR adds tests if it introduces a new test method, and modifies tests if it only edits existing ones. A PR can do both. Body-only edits and annotation changes leave the set of declared test names unchanged, so they never count as an added test.
Methodology for RQ2: Measuring Diff Coverage
Same patches, now executed. Run the suite twice per PR, and the difference isolates the agent's own tests.
1
Run with the agent's tests
Check out the PR's head commit and run the repository's whole suite under JaCoCo (Java) or pytest-cov (Python).
→ DiffCov_with
2
Run without them
Reverse-apply the test-only slice of the patch with git apply -R, then re-run the same suite on the same commit.
→ DiffCov_without
3
Label every added line
Snapshot each changed file at head and parse with srcML --position, tagging each added line with its construct: If, Return, Try-Catch, Throw …
→ miss rate by construct
Diff Coverage
=
covered added lines
covered + missed added lines
Summed over every changed code-under-test file, giving one value per PR.
Δ DiffCov = DiffCov_with − DiffCov_without
Scope: coverage obtained for 213 Java and 1,664 Python merged PRs; the paired with/without comparison applies to the 64 Java and 605 Python PRs that contain test changes. Gains tested with a one-sided Wilcoxon signed-rank test.
RQ2a: The Existing Suite Is an Incomplete Safety Net
Added lines covered by existing tests (%)
Median Java PR
71.1% covered
34.3% of PRs fully covered
Median Python PR
0% covered
64.8% of PRs have zero coverage
Distribution is bimodal in both languages, with mass at 0% and at 100%, but Python piles up at the zero end.