Gains:
- Ability to produce unit testing, edge cases, and coverage gap analysis with AI
- Ability to print test expectations based on the specification, not the current behavior of the code
- Ability to test whether a test actually protects by injecting errors
Writing tests is one of the most value-producing tasks that most developers put off. A good test suite is proof that the code works as expected and a lifeline for future changes. The problem is that writing tests is repetitive and time-consuming — exactly the kind of work where AI shines. But there is a catch: AI often tests the existing behavior of the code, not the behavior it should be. Managing this difference is the essence of this unit.
In this unit, you will learn unit testing (testing that tests a function alone, in isolation), edge case tests and generating test data with AI; closing gaps in test coverage; and why blindly trusting AI tests is dangerous.
The Two Sides of Testing: Fixing Behavior vs. verify
A test can serve two different purposes. The first is verification: it tests that the code is correct, that it complies with the specification. The second is regression protection: it freezes the behavior of the code today, so if someone accidentally changes it tomorrow, the test will break and notify.
AI is very good at the latter; It looks at the code and generates cases that test "what it's doing right now." But if the code is wrong from the start, AI can pin that wrong behavior as “correct.” So you must review the assertion of each test the AI produces: "The code returns 42 and the test expects 42" does not mean that 42 is the correct answer.
Caution: If the AI passes the test, it does not mean the code is "working"; it just means "it behaves as the AI expects". You decide whether the expectation is correct or not by looking at the specification.
Step by Step: Writing Robust Tests with AI
- Give the specification, not just the code. If you add the information "This function should do this", the AI can write the correct expectation; It will test the current behavior if you just provide the code.
- Ask for edge cases. Empty, null, zero, negative, too large, bad format, concurrency — explicitly claim off the happy path.
- Specify the testing framework and style. "use pytest", "Arrange-Act-Assert pattern", "let each test test one thing" etc.
- Check expectations (assertion). Compare with the specification that each assert checks for the correct value.
- Close gaps in scope. Give existing tests and ask "which branches and cases have not been tested?" make you ask; then verify additional tests produced.
Three Mini Cases
Case 1 — Coverage from 52% to 85%. Test coverage of one service module was 52%. The team fed the existing tests to the AI, had it list the untested branches and generate tests for them. With human review, coverage increased to 85%; In the process, the AI uncovered an actual bug (a path that returned the wrong error code) in a bug branch that had never been tested before.
Case 2 — The false expectation fixation trap. A money rounding function was actually wrong; Instead of rounding 2.675 to 2.67, it was rounding 2.67 instead of 2.68. The AI looked at the code and wrote assert round_money(2.675) == 2.67 — freezing the error as “true”. When the developer read the specification, he corrected the expectation and caught the real bug. Testing the rule, not the code, made the difference.
Case 3 — Edge state explosion. When asking the AI for only “edge cases” for a date range function; It produced 8 cases such as start=end, reverse interval, leap year February 29, different time zones and null interval. Two of these (reverse spacing and leap year) were actually causing the error. Considering these cases manually is often skipped; AI became an “edge-case brainstorming” partner here.
Four Copiable Templates
Specification-based test generation:
Role: A developer who writes tests. Framework: {{pytest/JUnit/Jest...}}.What the function SHOULD DO (specification): {{rule}}Write tests for the following function. Write expectations according to the specification, NOT the current output of the code. Happy path + add at least 4 edge cases. Let each test test one thing, use descriptive name. {{function}}
Edge case brainstorming:
List edge/failure cases that should be tried in testing for this function (null, null, breakpoints, bad format, concurrency, external error).For each case: input, expected behavior. DO NOT write code yet, just list.{{function}}
Coverage gap analysis:
Below are the functions and available tests. Which branches, conditions and cases have not been tested? List the deficiencies and write new tests only for the deficiencies. Don't repeat existing ones. Function:{{function}}Tests:{{existing_tests}}
Test data / mock object generation:
Generate realistic test data for {{function/service}} tests: valid samples, border samples and invalid samples separately. Suggest a simple mock behavior for the external dependency {{X}}. Using true confidential data/PII; Generate fake data.
Weak prompt / Strong prompt
Weak: "Write a test for this function."
Strong: "with pytest. Function apply_discount(total, percent) — rule: discount must be 0%–30%, out of bounds should throw ValueError, result should be rounded to 2 decimals. Write expectations by this RULE (not by code). Happy path + these edge cases: 0%, 30%, 31% (error), negative, total=0. [code]"
He gives the strong release rule and says "write the expectation according to the rule, not the code"; This single sentence closes the trap of AI fixing misbehavior.
Test type
AI contribution
human control
Happy road unit testing
fast skeleton
Is the expectation correct?
Edge cases
Extensive brainstorming
Eliminate the irrelevant
Scope gap filling
Finds skipped branches
Confirm significance
Test data/mock
Produces realistic sample
No PII, realism control
Tests Manage Quality, Not Guarantee It
High test coverage gives confidence, but it can also be misleading: 100 percent coverage means "every line was run," not "every line is correct." It is easy to increase coverage with AI; The real value is in writing meaningful expectations. The value of a test is its ability to break and alert you when the code is broken. That's why AI-generated tests are based on the question "does the code really break when it changes?" Test it with the question; Deliberately breaking a line and seeing the test break (mutation idea) is proof that the test worked.
Tip: To see if a test the AI writes works, create a small bug in the code (e.g. change a + to a -) and see if the test breaks. If it doesn't break, that test doesn't protect you.
Common mistakes
- Asking for a test without giving the rule. The model freezes current behavior; fixes the error as "true".
- Accepting expectations without reading them. Testing is misleading if you don't check that the asserts are checking for the correct value.
- Just testing the happy path. Real errors live on the margins; Ask for edge cases explicitly.
- Mistaking the scope for the purpose. A high percentage is no guarantee of correct behavior.
- Making real/hidden data as test data. Customer data or secrets should not enter testing and storage; Generate synthetic data.
In summary
AI takes much of the repetitive burden out of writing tests: it produces fast skeletons, large lists of edge cases, and coverage gap analyses. But the most critical point is expectations: AI tends to test the current behavior of the code, whereas testing should be written according to the specification. Give the rule, check the expectations, enforce edge cases, and test whether the tests actually protect by injecting a bug. Test coverage is a tool, not a goal.
Application task
Select a function and first print a test to the AI by simply giving its code; Note the expectations. Then print the test again, giving the specification (required behavior) for the same function. Compare the expectations of the two test sets: are there any different, which one reveals a real bug? Finally, verify that one of the generated tests worked by adding an intentional bug to the code and seeing the test break.
checklist
- [ ] I distinguish whether the test is to fix or verify behavior.
- [ ] When I request a test, I give the rule (specification) that should be in place, not the code.
- [ ] I compare each generated assertion to the specification.
- [ ] I explicitly request edge and failure cases.
- [ ] I view percentage coverage as a tool, not a goal.
- [ ] I test whether a test actually protects by injecting errors.