Gains:
- Ability to prevent artificial intelligence from accepting erroneous behavior as 'correct' by calculating the expected value in unit tests independently of the acceptance rule
- Ability to print fast, independent and repeatable tests by applying AAA and FIRST principles and mocking external dependencies
- Ability to test tests with mutation (code breaking) and recognize difficult-to-test code as a design smell
The largest and fastest layer of the testing pyramid is unit testing — testing that verifies a function or small piece of code in isolation from everything else. Thousands of unit tests run in seconds and catch a bug while the code is still on the developer's screen. Artificial intelligence (AI) is perhaps most proficient at producing unit tests: you give it a function, AI produces dozens of tests. But this very convenience gives rise to the biggest trap: AI easily produces tests that “glow green but don't verify anything” or accept the current (perhaps faulty) behavior of the code as “correct”. In this unit you will learn how to write truly protective unit tests with AI and the relationship between testable code and AI.
Qualities of a good unit test: FIRST
Good unit tests follow FIRST principles: Fast, Independent (tests should not be dependent on each other), Repeatable (repeatable — same result in any environment), Self-validating (clear pass/fail), Timely (on time). Remind yourself of these principles when having AI produce tests; specifically ask that the test not depend on the outside world (actual database, network, clock) to be "independent" and "repeatable".
AAA pattern and expressive assert
A solid unit test follows the AAA structure: Arrange (prepare — set up inputs and dependencies), Act (execute — call the function under test), Assert (validate — compare the result with the expected value). The critical one is assert. The most common mistake AI makes is deriving the assert from the output of the code under test — the “whatever the code returns is true” logic. This makes the test meaningless. The correct way is to determine the expected value independently (from the acceptance criteria, calculate it manually).
Attention: If you tell the AI "write a test for this function", the AI may run the function and write its output as "expected". This test passes even if the function is false. Instead, say "you calculate the expected results according to these rules, do not reference the current output of the function."
Mocks, stubs and dependencies
Unit testing requires isolation. If your function depends on a database or API, they are replaced with mock objects (mock/stub — a controlled, dummy substitute for the real dependency) in testing. This makes the test rapid, independent and reproducible. AI can produce mock installation; But beware of excessive mocking: if you mock everything, the test will only verify "what the mock returns", not the actual logic. Balance: emulate the outside world, execute the real logic under test.
Testability and AI
There's an interesting feedback: code that's hard to test is often poorly designed code. If the AI has trouble writing tests to a function (too many dependencies, hidden global state, side effects), that's a design smell. Asking the AI “how would you refactor this code to make it testable” leads to both better testing and better code.
Parameterized tests and data diversity
Writing a separate test each time to verify the same rule with different inputs is both tedious and difficult to maintain. Parameterized testing — a structure that repeatedly runs the same test logic on a list of inputs and expected results — eliminates this repetition: a single test body is fed with dozens of input pairs. AI is very efficient at producing these input-expected outcome tables when you give it your acceptance rules; In particular, it systematically tabulates limit values and equivalence classes.
But there is a trap here too: the AI tends to derive the expected results in the generated table from the code under test. This error is even more dangerous in parameterized testing, because a single incorrect logic invalidates dozens of lines. Therefore, always have the expected result column calculated independently according to the acceptance rule and manually validate at least a few rows. Also ask for a description column "what does each row represent"; so when a row breaks you instantly see which state is broken.
Tip: Intentionally add a “trap row” to the parameterized test table — that is, knowingly mistype the result. If that line does not turn red when you run the test, your test is not actually verifying that situation. This is a quick mock-pass check.
Weak prompt / Strong prompt
Weak: "Write a unit test for this function."
Strong: Write [language/framework] unit tests for the "taxCalculate(amount, rate) function. Acceptance rule: result = amount * rate, rounded to 2 decimals; negative amount or rate throws an error; returns 0 if rate is 0. Use AAA structure. Manually calculate expected values according to THESE rules; do not reference the current output of the function. Cover bound and negative cases (0, negative, very large, round to decimals). Let the name of each test describe the rule it verifies. External dependency "No."
Powerful prompt; It gives the acceptance rule, independent expected value expectation, structure and edge cases. Thus, the test becomes the guardian of the rule, not the mirror of the code.
Unit test quality table
symptom
Bad test (fake-trust)
good test
assert
None or "not null"
Expected concrete value
Expected value source
Output of the function
Acceptance rule / manual calculation
addiction
Actual DB/network/hour
Insulated with mock/stub
edge case
Only happy road
limit, negative, error
When you break the code
remains green
turns red
Name
test1, testMethod
describes the rule it confirms
Four copyable templates
1) Rule-driven unit testing:
Your role: senior software test engineer.Write a unit test on the following function with [language/framework]: [signature].Acceptance rules: [rules].- Use AAA structure.- Manually calculate expected values according to THESE rules; DO NOT reference the current output of the function. - Cover the limit, negative, error and happy path with separate tests. - Let each test name describe the rule it verifies. - Mock external dependencies; Make the actual logic work.
2) Mutation resistance control:
Check out these unit tests. List 5 minor tweaks I could make to the code under test (a - instead of a +, a >= instead of a >, a boundary shift) and tell me for each one WHICH of these tests will turn red? If none are returned, the test is insufficient.Code + tests: [paste]
3) Testability review:
Why is it difficult to write a unit test for this function? Hidden addiction, global status, side effects, are there many responsibilities? Suggest minimal refactoring to make it testable; do not change behavior. Code: [paste]
4) Incomplete scenario completion:
The following function and available tests are given. List which behavior/edgecase has NEVER been tested (scope gap) and add a test for each. Function+tests: [paste]
three mini cases
Case 1 — Test mirroring the code. A developer had the AI write a test for the rounding function; 10 tests were green. In fact, the function was rounding in the wrong direction, but the AI had taken the expected values from the function's output, so the tests considered the error "true". When the expected values were manually calculated with the "rule-driven" template, 4 tests turned red and the real error was revealed.
Case 2 — The value of mutation control. One team relied on 45 unit tests. Tried 20 minor tweaks to the code with a "mutation robustness check"; tests caught only 11 of them. The remaining 9 disruptions passed silently. The team strengthened weak tests; An actual calculation error was caught by these enhanced tests in the next release.
Case 3 — Untestability is a design smell. The AI couldn't write tests for an ordering function, it constantly needed the real database. The "testability review" template showed that the function embedded database access. When the dependency injection was removed, tests could be written and the code became cleaner.
Common mistakes
- Deriving the expected value from code. The AI accepts the function output as "correct"; test that confirms faulty code.
- Test without assert or with trivial assert. "He didn't throw an error, he passed" logic; It doesn't confirm anything.
- Extreme mock. Mocking everything and testing only what the mock returns; real logic is not tested.
- Just the happy road. Bypassing limit, negative and error states.
- Not testing by breaking the code. Trusting green without checking for mutation.
- Ignoring untestability. Not recognizing and fixing bad design instead of pushing hard testing.
In summary
Unit tests are the fastest and largest layer of the testing pyramid; It catches the mistake at the cheapest moment. AI is very capable of producing unit tests, but its biggest pitfall is writing tests that assume incorrect behavior as "correct" by deriving the expected value from the code itself. Solution: give the acceptance rules, have the expected values calculated manually, enforce the AAA and FIRST principles, mock the outside world and run the actual logic, and test each test by mutation (breaking the code). Code that is difficult to test is a design sign that needs fixing.
Application task
Select a function that contains a business rule from your own project. Write acceptance rules and have the AI write tests with the “rule-driven unit testing” template; Have the expected values calculated manually. Then apply the “mutation robustness check”: make at least 5 small breaks in the code and measure how many tests turn red. Add new test for uncaught corruptions. Report how many disruptions were caught (such as mutation score).
checklist
- [ ] I gave the acceptance rules and had the expected values calculated manually.
- [ ] I made sure that the tests did not derive the expected value from the code.
- [ ] I have established independent testing following AAA and FIRST guidelines.
- [ ] I mocked the external dependencies and ran the actual logic.
- [ ] I covered limit, negative and error cases.
- [ ] By breaking the code (mutation) I proved that the tests do indeed protect.