A test suite can pass every check and still tell you nothing, because a model that writes both the code and its tests in one sitting tends to grade its own homework.
The failure mode is subtle: the model reads the branches it just wrote and builds assertions that walk down those same paths. Nothing fails because the test is a restatement of the implementation, not an independent claim about what the code should do. A useful gut check is asking what input value would make a given test fail; if there is no clean answer, the test has ceremony but no opinion.
The Details:
- Structural separation is the most reliable fix. Start a fresh session with only a function signature and a plain description of required behavior, no branch logic or implementation hints, so the model has to guess at correctness instead of copying it.
- Adversarial framing changes the posture entirely. Asking a model to try to break a function produces tests that hunt for failure, while asking it to write tests for a function produces tests that confirm success. The difference shows up directly in what gets caught.
- Property-based testing checks invariants that hold across all valid inputs, like a sort staying ordered or a serialize-deserialize round trip returning the original value. Tools such as Hypothesis and fast-check generate hundreds of cases automatically and shrink failures to the smallest example that breaks the rule.
- Mutation testing exposes suites that only narrate code instead of checking it. Deliberately flipping a comparison or dropping a case and watching whether any test notices reveals gaps that a passing suite hides. Tools like Stryker and PIT surface a list of surviving mutants worth reading, not just a score worth celebrating.
Bottom Line: AI can generate branches and even generate tests, but it cannot generate the definition of correct from code that is itself the thing being questioned, so that judgment has to stay human.