A question is becoming more common on teams that write software with AI assistants: if AI writes the code, generates unit tests, runs end-to-end (E2E) checks, and everything passes, why do we still need code review?
The short answer is that a green test suite proves the checks someone chose to run have passed; it does not prove that every necessary check exists.
This is not a choice between tests and reviewers. They are different, complementary mechanisms. And the faster we generate code with AI, the more important that distinction becomes.
The trap of tests that confirm the same assumption
Imagine an assistant implements an endpoint that cancels orders. The real requirement is simple:
A user may cancel their own orders, but never another user’s order.
The implementation has an omission:
def cancel_order(order_id, user):
order = repository.get(order_id)
order.status = "cancelled"
repository.save(order)
return {"status": "cancelled"}
The function never checks whether the order belongs to the user. Yet the generated test may be entirely green:
def test_cancel_own_order():
result = cancel_order(own_order_id, authenticated_user)
assert result["status"] == "cancelled"
That test proves a user can cancel an order. It does not prove that other users are forbidden from cancelling it. Even an E2E test that logs in, clicks “Cancel,” and checks the result can pass if it exercises only the legitimate path.
This illustrates a particular risk of AI-assisted development: when code and tests come from the same incomplete reading of a requirement, both can repeat the same mistake. The problem can also arise when a human writes both; AI did not create it, but it can increase the volume of changes produced without a second perspective.
A reviewer who compares the implementation with the requirement should ask: where is order ownership checked? And request at least one negative test:
def test_cannot_cancel_someone_elses_order():
with pytest.raises(PermissionDenied):
cancel_order(other_users_order_id, authenticated_user)
The fix is not simply adding a decorative test. Authorization must be enforced server-side, in the operation that changes state, with evidence that an unauthorized request is rejected.
This example is intentionally simplified. A real application must also consider service-layer enforcement, tenant isolation, and avoiding partial state changes when a request fails.
What each quality layer can answer
| Layer | Main question | What it may miss |
|---|---|---|
| Unit tests | Does this function produce the expected result for the cases written? | Unimagined cases, misunderstood requirements, cross-component effects. |
| Integration tests | Do components and services work together? | Unexercised paths and design or security problems outside their scenarios. |
| E2E tests | Can a real workflow succeed from start to finish? | Alternative paths, permission abuse, races, and errors beyond the scripted flow. |
| Independent code review | Is this change correct for the requirement and the broader system? | Reviewers can also be wrong; outcomes depend on context, expertise, and review depth. |
Review does not replace tests: it can discover which tests are missing and why. Tests then turn a finding into repeatable regression protection.
Six problems a green suite may not catch
- Authorization and security: the legitimate user can act, but no test confirms an unauthorized user is rejected.
- Race conditions: two workers process the same event simultaneously. Sequential tests pass; production duplicates the effect.
- Business logic mistakes: the system accurately implements a rule that was never requested.
- Cross-component contracts: an API silently changes a field’s meaning while the modified function’s tests remain green.
- Privacy and data handling: code sends information to the wrong recipient or logs sensitive data; the happy path does not check it.
- Maintainability and architecture: the change works today but introduces coupling, complexity, or an unnecessary dependency that makes future work harder.
The Google Engineering Practices guide asks reviewers to examine not just functionality but also design, complexity, concurrency, and the quality of the tests themselves. OWASP highlights business logic, authorization controls, and trust boundaries as areas where automated checks may fall short.
What about 90% or even 100% test coverage?
Coverage indicates which lines or branches were executed by tests. It does not certify that assertions are meaningful or that correct behavior was actually checked.
A test that calls the wrong function and barely asserts that it does not crash can raise coverage without providing much confidence. That is why Martin Fowler explains that coverage percentages are more useful for identifying untested areas than as a single quality score.
A coverage target can be a useful warning signal, but it should not become an automatic license to merge.
Can another AI act as reviewer?
Yes, as another layer. An agent that did not write the code can read the diff, compare requirements, propose adversarial scenarios, and suggest missing tests. It can be particularly useful as a fast first pass.
But independent context is not a correctness guarantee. Two models may share biases, lack important context, or approve an incorrect solution. Changes involving permissions, payments, privacy, or infrastructure benefit from qualified human review and, when appropriate, specialized security testing.
Automated review should not stop at “looks good.” A valuable finding describes a concrete risk, where it appears, and how to reproduce it.
A practical workflow for every pull request
- Define intent: what changes, what must remain unchanged, and who is allowed to perform each action.
- Implement: use AI or write manually, with unit and integration tests including negative cases.
- Run automated checks: linting, static analysis, tests, builds, and relevant E2E flows.
- Review from another perspective: request an independent code review focused on business logic, security, concurrency, design, and missing test cases.
- Fix and add regression tests: when a bug is found, demonstrate it with a test that fails before the fix and passes afterward, when feasible.
- Merge against explicit criteria: green checks, relevant findings resolved, and the approvals appropriate to the risk.
Not every change needs a deep audit. A typo fix and an authorization-system change have very different risk profiles. Review effort can be proportional, but “the tests passed” should not be confused with “this is proven safe and correct.”
Conclusion
Unit tests answer: “Does what we tested work?”
E2E adds: “Does this complete journey work?”
Code review asks something else: “Did we test the right thing, miss an important scenario, or introduce a change that does not fit the system?”
Confidence comes from the combination: tests + E2E + independent review, with depth matched to risk. No layer is infallible; together they make it much harder for a mistaken assumption to reach production.