agentleFS
TrarothTraroth/claude-dev-framework/skills/jml-test-generation/SKILL.md

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.

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
  1. Distinctive:

    When the user says "generate tests from JML", "test the contracts", or "are the JML specs covered"

    line 16
  2. Very specific:

    When an @ensures uses ==> (implication), generate tests for both branches.

    line 132
  3. Very specific:

    When a @requires or @ensures uses quantifiers (\forall, \exists), derive boundary tests:

    line 158

≈ “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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

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:

  1. The class source file (with JML annotations)
  2. The corresponding interface (if applicable)
  3. The existing test files (contract test, black-box test, white-box test)
  4. INVARIANTS.md for 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 holds

Example:

// @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:

  1. Sets up a known pre-state
  2. Calls the method
  3. 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 clauseMethodTest exists?Test name

Report any uncovered clause.


Test placement

Derivation sourceTest level
@invariant (observable)Black-box test
@requires / @ensures (public API)Black-box test
@invariant (internal structure)White-box test

Output format

  1. Coverage summary — how many JML clauses exist, how many are covered
  2. Missing tests — list of JML clauses without corresponding tests, with suggested test method signatures
  3. Generated tests — if asked to write tests, provide the complete test methods with comments linking back to the JML clause

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.