Test writing
Decide what to test, structure tests with arrange/act/assert, and avoid common pitfalls. Use when the user asks to write, add, or improve tests for existing code, or wants test coverage for a new feature.From its SKILL.md
npx -y skills add kwhorne/elyra-skills --skill test-writingAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its file declares
Copied from the file, not written here
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
4.5 KB, ~1.1k tokens by cl100k_base, as published. Nobody here has run it
Test Writing
Tests exist to catch regressions and document intent. Coverage numbers are a side effect, not a goal.
When to use
- "Write tests for this function/class/module"
- "Add tests for the new feature"
- "Improve test coverage in X"
- "This isn't tested — fix that"
Procedure
1. Detect the stack
Find the existing test framework and conventions before writing anything new:
# Quick stack sniff
ls package.json composer.json pyproject.toml Cargo.toml go.mod 2>/dev/null
grep -l 'vitest\|jest\|pytest\|phpunit\|pest\|mocha\|rspec' \
package.json composer.json pyproject.toml 2>/dev/null
Then read one existing test file to match style: file location, naming (*.test.ts vs *_test.go vs test_*.py), helpers, factories.
Always match existing conventions. A "better" test style in an inconsistent codebase is a worse test.
2. Decide what to test
For each unit under test, ask:
| Category | Examples |
|---|---|
| Happy path | Typical input → expected output |
| Edge cases | Empty, null, zero, one, max, boundary, off-by-one |
| Error paths | Bad input, missing dependency, network/IO failure |
| Branches | Every meaningful if / switch / match arm |
| Invariants | Things that must always be true (idempotency, sort order, …) |
Don't test:
- Trivial getters/setters
- Third-party library internals
- Generated code
- Implementation details (private methods, internal call order)
3. Structure each test: Arrange / Act / Assert
def test_user_can_redeem_valid_coupon():
# Arrange — set up the world
user = make_user()
coupon = make_coupon(code="SAVE10", percent=10)
# Act — do the one thing
result = redeem(user, coupon)
# Assert — check the outcome
assert result.discount == 10
assert coupon.used_by == user.id
One Act per test. If you have two, it's two tests.
4. Name tests for behavior, not implementation
Test names are sentences the codebase tells you when something breaks.
- ✅
redeem_marks_coupon_as_used_for_redeeming_user - ✅
it returns 404 when product does not exist - ❌
test_redeem_1 - ❌
testRedeemFunction
5. Assert on observable behavior
Tests should fail when the behavior changes, not when the code structure changes.
- ✅ "After
register(), the user can log in with that email" - ❌ "After
register(),userRepository.savewas called once with{email: ...}"
The second one breaks the moment you rename a repo, even though behavior is unchanged.
6. Run and iterate
- Run the new test → it must pass
- Break the code under test deliberately → the test must fail (otherwise it doesn't actually test anything)
- Run the full suite → must still pass
Common pitfalls
| Pitfall | Why it hurts | Fix |
|---|---|---|
| Testing mocks instead of behavior | Refactors break tests even when behavior is preserved | Assert on outputs/state, not on mock calls |
| Shared mutable fixtures | Tests pass/fail depending on order | Fresh setup per test, or explicit factories |
| Time-dependent tests | Flaky on slow CI / different timezones | Inject a clock, freeze time |
| Hitting the network / real DB | Slow + flaky | In-memory DB, recorded fixtures, contract tests |
| Asserting too much | Brittle to unrelated changes | Assert only what the test name claims |
| Asserting too little | Test passes even when broken | Mutation test: break the code, watch the test fail |
| Sleep/setTimeout in tests | Flaky | Poll with timeout, or use the framework's async primitives |
Output format
Report:
## Tests added: <scope>
**Framework:** <pytest / vitest / phpunit / …>
**Coverage:**
- ✅ Happy path: …
- ✅ Edge cases: …
- ✅ Error paths: …
**Deliberately not covered (and why):**
- …
**How to run:**
\`\`\`
<exact command>
\`\`\`
Anti-patterns
- ❌ Writing tests after deploying — you tested in prod, then doubled down
- ❌ Chasing 100% coverage by testing trivia
- ❌ One giant test that does seven things
- ❌ Tests that pass without an assertion (yes, this happens)
- ❌ Skipped/commented-out tests left in the codebase
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.