jml-test-generation
Skill1 starsChanged 7 months agoMITView on GitHub
Generate test cases derived from JML specifications (invariants, requires, ensures) for a class in the project. Use this skill after JML contracts are in place and tests need to be written or reviewed. The goal is to ensure that every JML contract is exercised by at least one test.
In this repository12 other files it gives its agents.
- api-consistency-checkSkill
- architecture-drift-checkSkill
- design-reviewSkill
- jml-completeness-checkSkill
- jml-conformance-checkSkill
- jml-designSkill
- new-classSkill
- refactor-classSkill
- static-analysisSkill
- test-coverage-reviewSkill
- update-backlogSkill
- update-readmeSkill
The file
We split this skill into the rules it gives an agent
3 rules. About 0 are common, 1 is distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 16
When the user says "generate tests from JML", "test the contracts", or "are the JML specs covered"
- Very specific: line 132
When an @ensures uses ==> (implication), generate tests for both branches.
- Very specific: line 158
When a @requires or @ensures uses quantifiers (\forall, \exists), derive boundary tests:
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
JML Test Generation
When to run
- During step 6 of the workflow (tests — TDD) after JML contracts are written
- During step 12 (test coverage review) to identify gaps
- When the user says "generate tests from JML", "test the contracts", or "are the JML specs covered"
Background
There is no actively maintained tool for automatic test generation from JML specifications (JMLUnit and JMLUnit-NG are dormant since ~2014 and do not support modern Java). This skill relies on Claude reading the JML annotations and systematically deriving test cases from them.
OpenJML RAC (Runtime Assertion Checking) can complement this approach: code
compiled with the openjml Maven profile will check pre/post-conditions and
invariants at runtime during test execution.
Inputs
Before starting, read:
- The class source file (with JML annotations)
- The corresponding interface (if applicable)
- The existing test files (contract test, black-box test, white-box test)
INVARIANTS.mdfor the relevant class section
Test derivation rules
1. From @invariant — invariant preservation tests
For each class-level invariant, generate tests that verify it holds after every kind of mutation.
Pattern:
For each invariant I:
For each mutation method M (add, remove, set, clear, put, ...):
1. Create an instance in a known state
2. Call M
3. Assert I still holdsExample:
// @invariant: size() >= 0
@Test
void sizeNeverNegativeAfterRemoveOnEmpty() {
list.clear();
assertThrows(IndexOutOfBoundsException.class, () -> list.remove(0));
assertTrue(list.size() >= 0);
}These tests belong in the black-box test (observable through public API).
2. From @requires — pre-condition tests
For each @requires clause, generate two kinds of tests:
a) Nominal case — call with inputs that satisfy the pre-condition, verify
the method succeeds and the @ensures hold.
b) Violation case — call with inputs that violate the pre-condition, verify the expected exception is thrown.
Example:
// @requires 0 <= index && index < size()
@Test
void getWithValidIndex() {
list.add("a");
assertEquals("a", list.get(0));
}
@Test
void getWithNegativeIndexThrows() {
list.add("a");
assertThrows(IndexOutOfBoundsException.class, () -> list.get(-1));
}
@Test
void getWithIndexEqualToSizeThrows() {
list.add("a");
assertThrows(IndexOutOfBoundsException.class, () -> list.get(1));
}3. From @ensures — post-condition tests
For each @ensures clause, generate a test that:
- Sets up a known pre-state
- Calls the method
- Asserts the post-condition
Pay special attention to \old() expressions — they require capturing state
before the call.
Example:
// @ensures size() == \old(size()) + 1
@Test
void addIncrementsSizeByOne() {
int before = list.size();
list.add("x");
assertEquals(before + 1, list.size());
}4. From conditional @ensures — branch coverage
When an @ensures uses ==> (implication), generate tests for both branches.
Example:
// @ensures \old(contains(o)) ==> size() == \old(size()) - 1
// @ensures !\old(contains(o)) ==> size() == \old(size())
@Test
void removeExistingElementDecreasesSize() {
list.add("a");
int before = list.size();
list.remove("a");
assertEquals(before - 1, list.size());
}
@Test
void removeAbsentElementKeepsSize() {
list.add("a");
int before = list.size();
list.remove("z");
assertEquals(before, list.size());
}5. Boundary cases from quantifiers
When a @requires or @ensures uses quantifiers (\forall, \exists),
derive boundary tests:
- Empty collection (vacuously true quantifiers)
- Single element
- Multiple elements
Coverage matrix
For each class, build a mental coverage matrix:
| JML clause | Method | Test exists? | Test name |
Report any uncovered clause.
Test placement
| Derivation source | Test level |
@invariant (observable) | Black-box test |
@requires / @ensures (public API) | Black-box test |
@invariant (internal structure) | White-box test |
Output format
- Coverage summary — how many JML clauses exist, how many are covered
- Missing tests — list of JML clauses without corresponding tests, with suggested test method signatures
- Generated tests — if asked to write tests, provide the complete test methods with comments linking back to the JML clause
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

