Skip to content

Commit 98db0ec

Browse files
committed
adding Claude skill
1 parent 535a6b2 commit 98db0ec

2 files changed

Lines changed: 216 additions & 7 deletions

File tree

README.md

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -405,21 +405,35 @@ class ShopFixture {
405405
}
406406
```
407407

408-
## GitHub Copilot integration
408+
## AI coding assistant integration
409409

410-
If you use test-arranger and GitHub Copilot side by side, you have likely noticed that Copilot keeps generating verbose, hand-crafted test data setup long builder chains, explicit constructors with hardcoded values exactly the boilerplate that test-arranger was designed to eliminate.
411-
This happens because Copilot has no built-in knowledge of test-arranger: it does not know about `Arranger.some()`, `CustomArranger`, `Rearranger`, or Fixtures, so it falls back to whatever patterns it has seen most often in open-source code.
410+
If you use test-arranger alongside an AI coding assistant, you have likely noticed that the assistant keeps generating verbose, hand-crafted test data setup - long builder chains, explicit constructors with hardcoded values - exactly the boilerplate that test-arranger was designed to eliminate.
411+
This happens because AI assistants have no built-in knowledge of test-arranger: they do not know about `Arranger.some()`, `CustomArranger`, `Rearranger`, or Fixtures, so they fall back to whatever patterns they have seen most often in open-source code.
412412

413-
Fortunately, GitHub Copilot supports [custom instructions](https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot) — a repository-level file that tells Copilot how code in that project should be written.
414-
And adding test-arranger instructions bridges this gap.
413+
Fortunately, most AI coding assistants support some form of custom instructions — a way to tell the tool how code in your project should be written.
414+
The `ai/java/` directory in this repository contains ready-made instruction that bridge this gap.
415415

416-
To enable this in your project, copy [`ai/java/copilot-instructions.md`](ai/java/copilot-instructions.md) from this repository into your own repository at:
416+
### GitHub Copilot
417+
418+
GitHub Copilot supports [custom instructions](https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot) via a repository-level file.
419+
Copy [`ai/java/copilot-instructions.md`](ai/java/copilot-instructions.md) into your own repository at:
417420

418421
```
419422
.github/copilot-instructions.md
420423
```
421424

422-
The instructions are tailored for **Java projects**.
425+
Copilot will automatically pick up this file and apply the guidelines whenever it generates or edits test code in your repository.
426+
427+
### Claude
428+
429+
Claude supports [custom slash commands and skills](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview) via `.claude/` project files.
430+
Copy [`ai/java/claude_tester_SKILL.md`](ai/java/claude_tester_SKILL.md) into your own repository at:
431+
432+
```
433+
.claude/skills/tester.md
434+
```
435+
436+
Claude will automatically apply this skill when asked to create or update tests, using the test-arranger guidelines defined in the skill file.
423437

424438
## Articles and blog posts
425439

ai/java/claude_tester_SKILL.md

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
---
2+
name: tester
3+
description: test creation guidelines and conventions; use when user asks to create new tests or update existing ones
4+
---
5+
6+
# Testing Guidelines (Project Conventions)
7+
8+
## Test structure
9+
10+
### Instruction
11+
12+
- Always use the **given / when / then** pattern.
13+
- Use the exact section comments:
14+
- `// given`
15+
- `// when`
16+
- `// then`
17+
- Keep the `given` section minimal: set only fields relevant to the scenario.
18+
- Assertions should reflect **behavior and intent**, not incidental object details.
19+
20+
Practical rules for generated tests
21+
- Do not manually construct large domain objects unless necessary.
22+
- Generate with `Arranger.some(...)` first.
23+
- Override only scenario-relevant fields.
24+
- Prefer comparing against arranged values instead of hardcoded constants unless the constant is the test's purpose.
25+
- Keep tests concise and intention-revealing.
26+
27+
### Example
28+
29+
``` java
30+
@Test
31+
void shouldCreateReportForProductBrand() {
32+
// given
33+
Product product = Arranger.some(Product.class);
34+
35+
// when
36+
Report report = sut.createBrandReport(List.of(product));
37+
38+
// then
39+
assertThat(report.getBrand()).isEqualTo(product.getBrand());
40+
}
41+
```
42+
43+
## Test data generation (default: Arranger)
44+
45+
### Instruction
46+
47+
**By default, use test-arranger to generate test data.**
48+
Do not hand-build large objects with constructors/builders unless the test truly depends on specific values.
49+
Avoid using mocks, prefer instances filled with random data by the test-arranger.
50+
51+
#### Basic usage (Java)
52+
53+
- `Arranger.some(X.class)` → fully populated random instance
54+
- `Arranger.some(X.class, "fieldName")` → instance with given field unset
55+
- `Arranger.someObjects(X.class, n)` → stream of `n` instances
56+
- `Arranger.someEmail()`, `Arranger.someLong()`, `Arranger.someText()`
57+
- `Arranger.someFrom(list)` → random element from list
58+
59+
### Example
60+
61+
``` java
62+
// given
63+
Product product = Arranger.some(Product.class);
64+
product.setBrand("VIP");
65+
```
66+
67+
## Adjusting arranged data
68+
69+
### Instruction
70+
Prefer expressing intent by modifying only fields that matter.
71+
- mutate if possible
72+
- use Arranger overrides for non-mutable types (the class is immutable, there is no with/toBuilder, direct mutation is not possible)
73+
74+
### Example
75+
76+
#### 1. Mutate if possible
77+
78+
``` java
79+
Product product = Arranger.some(Product.class);
80+
product.setBrand("VIP");
81+
```
82+
83+
### 2. Use Arranger overrides (for non-mutable types)
84+
85+
``` java
86+
Product product = Arranger.some(Product.class, Map.of(
87+
"brand", () -> "VIP",
88+
"price", () -> BigDecimal.TEN
89+
));
90+
```
91+
92+
## Rearranger (copy + selective overrides)
93+
94+
### Instruction
95+
Use **Rearranger** when you already have a valid instance and want to tweak a few fields.
96+
97+
When to prefer Rearranger
98+
- You want to start from a valid domain object
99+
- Only a few fields differ
100+
- You want to keep the rest realistic and consistent
101+
102+
Important
103+
- Rearranger performs a **shallow copy**.
104+
- Nested mutable objects are shared between original and copy.
105+
- Constructor logic may be bypassed in fallback scenarios; restore invariants via overrides if needed.
106+
107+
### Example
108+
``` java
109+
User original = Arranger.some(User.class);
110+
111+
// given
112+
User admin = Rearranger.copy(original, Map.of(
113+
"role", () -> "ADMIN",
114+
"active", () -> true
115+
));
116+
```
117+
118+
## Custom Arrangers (encode invariants once)
119+
120+
### Instruction
121+
122+
If random-by-type generation violates domain rules, create a `CustomArranger<T>`.
123+
124+
Rules:
125+
- Use custom arrangers when invariants must always hold.
126+
- Add well-named factory methods only when tests require specific variants.
127+
- `Arranger.some(X.class)` will automatically use `XArranger` if present.
128+
129+
### Example
130+
``` java
131+
class ProductArranger extends CustomArranger<Product> {
132+
133+
@Override
134+
protected Product instance() {
135+
Product product = enhancedRandom.nextObject(Product.class);
136+
product.setPrice(
137+
BigDecimal.valueOf(Arranger.somePositiveLong(9_999L))
138+
);
139+
return product;
140+
}
141+
}
142+
```
143+
144+
## When to use what
145+
146+
Situation Prefer
147+
-------------------------------------------- ------------------------------------
148+
Need a fresh random object `Arranger.some(X.class)`
149+
Need multiple objects `Arranger.someObjects(X.class, n)`
150+
Start from valid instance and tweak fields `Rearranger.copy(...)`
151+
Enforce domain invariants globally `CustomArranger<T>`
152+
Reuse complex multi-entity setup `Fixture`
153+
154+
------------------------------------------------------------------------
155+
156+
## Fixtures (reuse complex setups)
157+
158+
### Instruction
159+
160+
Use fixtures when tests repeatedly require a specific constellation of multiple related objects.
161+
162+
A fixture:
163+
- Creates multiple domain objects
164+
- Links them correctly
165+
- Hides setup complexity
166+
- Expresses domain meaning
167+
168+
Use `Fixture` suffix for such classes.
169+
170+
Guidelines:
171+
- Use fixtures for **reused object graphs**, not simple objects.
172+
- Keep fixture methods well-named and domain-oriented.
173+
- Internally use Arranger and custom arrangers.
174+
- Avoid duplicating complex setup logic across tests.
175+
176+
### Example
177+
178+
``` java
179+
class ShopFixture {
180+
181+
private final Repository repository;
182+
183+
ShopFixture(Repository repository) {
184+
this.repository = repository;
185+
}
186+
187+
void shopWithNineProductsAndFourCustomers() {
188+
Arranger.someObjects(Product.class, 9)
189+
.forEach(repository::save);
190+
191+
Arranger.someObjects(Customer.class, 4)
192+
.forEach(repository::save);
193+
}
194+
}
195+
```

0 commit comments

Comments
 (0)