@grimoire-cc/cli 0.13.3 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +15 -5
- package/dist/bin.js.map +1 -1
- package/dist/commands/agent-paths.d.ts +11 -0
- package/dist/commands/agent-paths.d.ts.map +1 -0
- package/dist/commands/agent-paths.js +69 -0
- package/dist/commands/agent-paths.js.map +1 -0
- package/dist/commands/agent-skills.d.ts +10 -0
- package/dist/commands/agent-skills.d.ts.map +1 -0
- package/dist/commands/agent-skills.js +159 -0
- package/dist/commands/agent-skills.js.map +1 -0
- package/dist/commands/config.d.ts +7 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +62 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/list.d.ts.map +1 -1
- package/dist/commands/list.js +237 -75
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/update.d.ts +1 -2
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +18 -0
- package/dist/commands/update.js.map +1 -1
- package/dist/enforce.d.ts +9 -9
- package/dist/enforce.d.ts.map +1 -1
- package/dist/enforce.js +56 -23
- package/dist/enforce.js.map +1 -1
- package/dist/frontmatter.d.ts +16 -0
- package/dist/frontmatter.d.ts.map +1 -0
- package/dist/frontmatter.js +74 -0
- package/dist/frontmatter.js.map +1 -0
- package/dist/grimoire-config.d.ts +6 -0
- package/dist/grimoire-config.d.ts.map +1 -0
- package/dist/grimoire-config.js +23 -0
- package/dist/grimoire-config.js.map +1 -0
- package/dist/prompt.d.ts.map +1 -1
- package/dist/prompt.js +13 -8
- package/dist/prompt.js.map +1 -1
- package/dist/remove.d.ts +4 -0
- package/dist/remove.d.ts.map +1 -1
- package/dist/remove.js +8 -0
- package/dist/remove.js.map +1 -1
- package/dist/resolve.d.ts.map +1 -1
- package/dist/resolve.js +12 -5
- package/dist/resolve.js.map +1 -1
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +45 -2
- package/dist/setup.js.map +1 -1
- package/dist/summary.d.ts.map +1 -1
- package/dist/summary.js +9 -0
- package/dist/summary.js.map +1 -1
- package/package.json +1 -1
- package/packs/dev-pack/agents/grimoire.tdd-specialist.md +194 -27
- package/packs/dev-pack/grimoire.json +0 -38
- package/packs/dev-pack/skills/grimoire.conventional-commit/SKILL.md +69 -65
- package/packs/dotnet-pack/agents/grimoire.csharp-coder.md +110 -113
- package/packs/dotnet-pack/grimoire.json +23 -5
- package/packs/dotnet-pack/skills/grimoire.unit-testing-dotnet/SKILL.md +252 -0
- package/packs/{dev-pack/skills/grimoire.tdd-specialist → dotnet-pack/skills/grimoire.unit-testing-dotnet}/reference/anti-patterns.md +78 -0
- package/packs/dotnet-pack/skills/grimoire.unit-testing-dotnet/reference/tdd-workflow-patterns.md +259 -0
- package/packs/frontend-pack/agents/grimoire.angular-coder.md +193 -0
- package/packs/frontend-pack/grimoire.json +7 -0
- package/packs/go-pack/grimoire.json +19 -0
- package/packs/go-pack/skills/grimoire.unit-testing-go/SKILL.md +256 -0
- package/packs/go-pack/skills/grimoire.unit-testing-go/reference/anti-patterns.md +244 -0
- package/packs/go-pack/skills/grimoire.unit-testing-go/reference/tdd-workflow-patterns.md +259 -0
- package/packs/python-pack/grimoire.json +19 -0
- package/packs/python-pack/skills/grimoire.unit-testing-python/SKILL.md +239 -0
- package/packs/python-pack/skills/grimoire.unit-testing-python/reference/anti-patterns.md +244 -0
- package/packs/python-pack/skills/grimoire.unit-testing-python/reference/tdd-workflow-patterns.md +259 -0
- package/packs/rust-pack/grimoire.json +29 -0
- package/packs/rust-pack/skills/grimoire.unit-testing-rust/SKILL.md +243 -0
- package/packs/rust-pack/skills/grimoire.unit-testing-rust/reference/anti-patterns.md +244 -0
- package/packs/rust-pack/skills/grimoire.unit-testing-rust/reference/tdd-workflow-patterns.md +259 -0
- package/packs/ts-pack/agents/grimoire.typescript-coder.md +36 -1
- package/packs/ts-pack/grimoire.json +27 -1
- package/packs/ts-pack/skills/grimoire.unit-testing-typescript/SKILL.md +255 -0
- package/packs/ts-pack/skills/grimoire.unit-testing-typescript/reference/anti-patterns.md +244 -0
- package/packs/ts-pack/skills/grimoire.unit-testing-typescript/reference/tdd-workflow-patterns.md +259 -0
- package/dist/commands/enforce-agent.d.ts +0 -5
- package/dist/commands/enforce-agent.d.ts.map +0 -1
- package/dist/commands/enforce-agent.js +0 -94
- package/dist/commands/enforce-agent.js.map +0 -1
- package/packs/dev-pack/skills/grimoire.tdd-specialist/SKILL.md +0 -248
- package/packs/dev-pack/skills/grimoire.tdd-specialist/reference/language-frameworks.md +0 -388
- package/packs/dev-pack/skills/grimoire.tdd-specialist/reference/tdd-workflow-patterns.md +0 -135
- package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/SKILL.md +0 -293
- package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/anti-patterns.md +0 -329
- package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/framework-guidelines.md +0 -361
- package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/parameterized-testing.md +0 -378
- package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/test-organization.md +0 -476
- package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/test-performance.md +0 -576
- package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/templates/tunit-template.md +0 -438
- package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/templates/xunit-template.md +0 -303
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: grimoire.tdd-specialist
|
|
3
|
-
description: "Language-agnostic TDD and unit testing specialist. Use when writing unit tests, adding test coverage, doing test-driven development, or setting up test infrastructure in ANY language. Auto-detects project language and test framework. Supports pytest, jest, vitest, mocha, junit, go test, cargo test, xunit, and more. Triggers: tdd, test-driven, unit test, write tests, test coverage, red-green-refactor."
|
|
4
|
-
version: 1.0.0
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# TDD Specialist
|
|
8
|
-
|
|
9
|
-
Language-agnostic test-driven development and unit testing guidance. Works with any language — detects the project's test stack automatically and applies universal TDD principles.
|
|
10
|
-
|
|
11
|
-
## Context
|
|
12
|
-
|
|
13
|
-
Test-driven development produces cleaner designs, fewer bugs, and enables confident refactoring. AI-assisted TDD works best with a structured workflow: humans define goals and approve test plans, AI executes the implementation. This skill provides the knowledge base for that workflow.
|
|
14
|
-
|
|
15
|
-
## Language & Framework Detection
|
|
16
|
-
|
|
17
|
-
### Step 1: Detect Language
|
|
18
|
-
|
|
19
|
-
Check for project manifest files to determine the primary language:
|
|
20
|
-
|
|
21
|
-
| File | Language |
|
|
22
|
-
|------|----------|
|
|
23
|
-
| `package.json` | JavaScript / TypeScript |
|
|
24
|
-
| `tsconfig.json` | TypeScript |
|
|
25
|
-
| `pyproject.toml`, `setup.py`, `setup.cfg` | Python |
|
|
26
|
-
| `go.mod` | Go |
|
|
27
|
-
| `Cargo.toml` | Rust |
|
|
28
|
-
| `*.csproj`, `*.sln` | C# / .NET |
|
|
29
|
-
| `pom.xml`, `build.gradle`, `build.gradle.kts` | Java / Kotlin |
|
|
30
|
-
| `Gemfile` | Ruby |
|
|
31
|
-
| `mix.exs` | Elixir |
|
|
32
|
-
| `Package.swift` | Swift |
|
|
33
|
-
|
|
34
|
-
### Step 2: Detect Test Framework
|
|
35
|
-
|
|
36
|
-
**Always check existing test files first** — match whatever the project already uses.
|
|
37
|
-
|
|
38
|
-
If no existing tests, infer from config:
|
|
39
|
-
|
|
40
|
-
- **JavaScript/TypeScript**: Check `package.json` devDependencies for `vitest`, `jest`, `mocha`. Default: Vitest for Vite projects, Jest otherwise.
|
|
41
|
-
- **Python**: Check for `pytest` in dependencies or `[tool.pytest]` in `pyproject.toml`. Default: pytest.
|
|
42
|
-
- **Go**: Built-in `testing` package. Check for `testify` in `go.mod`.
|
|
43
|
-
- **Rust**: Built-in `#[test]`. Check for `mockall` in `Cargo.toml`.
|
|
44
|
-
- **C#/.NET**: Check `.csproj` for xUnit/NUnit/MSTest references. **If `grimoire.dotnet-unit-testing` skill is available, defer to it.**
|
|
45
|
-
- **Java/Kotlin**: Check for JUnit 5 (`junit-jupiter`), Mockito in build files. Default: JUnit 5 + Mockito.
|
|
46
|
-
- **Ruby**: Check for `rspec` or `minitest` in Gemfile. Default: RSpec.
|
|
47
|
-
|
|
48
|
-
### Step 3: Detect Conventions
|
|
49
|
-
|
|
50
|
-
Read 2-3 existing test files to learn:
|
|
51
|
-
- File naming convention (e.g., `test_*.py`, `*.test.ts`, `*_test.go`)
|
|
52
|
-
- Directory structure (e.g., `tests/`, `__tests__/`, co-located)
|
|
53
|
-
- Assertion style and helper patterns
|
|
54
|
-
- Mocking approach
|
|
55
|
-
|
|
56
|
-
## Workflow
|
|
57
|
-
|
|
58
|
-
### Step 1: Analyze
|
|
59
|
-
|
|
60
|
-
- Read the source code under test
|
|
61
|
-
- Detect language and test framework (steps above)
|
|
62
|
-
- Identify dependencies that need mocking/stubbing
|
|
63
|
-
- Check for existing test patterns in the project
|
|
64
|
-
- Understand the expected behavior and edge cases
|
|
65
|
-
|
|
66
|
-
### Step 2: Plan (REQUIRES USER APPROVAL)
|
|
67
|
-
|
|
68
|
-
Present test cases as **method/function names only**, grouped by category. Do NOT include test bodies.
|
|
69
|
-
|
|
70
|
-
**Format:**
|
|
71
|
-
|
|
72
|
-
```plain
|
|
73
|
-
## Test Plan for [Module/Class.Method]
|
|
74
|
-
|
|
75
|
-
**Language:** [detected] | **Framework:** [detected] | **File:** [proposed test file path]
|
|
76
|
-
|
|
77
|
-
### Success Scenarios
|
|
78
|
-
- test_process_order_with_valid_input_returns_success
|
|
79
|
-
- test_process_order_with_discount_applies_correctly
|
|
80
|
-
|
|
81
|
-
### Validation Failures
|
|
82
|
-
- test_process_order_with_null_input_raises_value_error
|
|
83
|
-
- test_process_order_with_empty_items_raises_validation_error
|
|
84
|
-
|
|
85
|
-
### Error Handling
|
|
86
|
-
- test_process_order_when_repository_fails_raises_service_error
|
|
87
|
-
|
|
88
|
-
### Edge Cases
|
|
89
|
-
- test_process_order_with_maximum_items_succeeds
|
|
90
|
-
|
|
91
|
-
Do you approve this test plan? I will proceed only after your confirmation.
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
**STOP and WAIT for user approval before proceeding.**
|
|
95
|
-
|
|
96
|
-
### Step 3: Write (ONLY after approval)
|
|
97
|
-
|
|
98
|
-
Implement tests following:
|
|
99
|
-
- Detected framework conventions
|
|
100
|
-
- AAA (Arrange-Act-Assert) or Given-When-Then pattern
|
|
101
|
-
- Language-idiomatic naming and style
|
|
102
|
-
- Proper mocking/stubbing at boundaries
|
|
103
|
-
|
|
104
|
-
### Step 4: Explain
|
|
105
|
-
|
|
106
|
-
- Present the complete test file
|
|
107
|
-
- Explain what each test validates
|
|
108
|
-
- Highlight assumptions made
|
|
109
|
-
- Suggest additional scenarios if relevant
|
|
110
|
-
|
|
111
|
-
## Universal Testing Principles
|
|
112
|
-
|
|
113
|
-
### Arrange-Act-Assert (AAA)
|
|
114
|
-
|
|
115
|
-
Structure every test with clearly separated phases. Use comments for clarity:
|
|
116
|
-
|
|
117
|
-
```python
|
|
118
|
-
# Python / pytest
|
|
119
|
-
def test_calculate_total_with_discount_applies_percentage():
|
|
120
|
-
# Arrange
|
|
121
|
-
cart = Cart(items=[Item(price=100), Item(price=50)])
|
|
122
|
-
discount = PercentageDiscount(10)
|
|
123
|
-
|
|
124
|
-
# Act
|
|
125
|
-
total = cart.calculate_total(discount)
|
|
126
|
-
|
|
127
|
-
# Assert
|
|
128
|
-
assert total == 135.0
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
```typescript
|
|
132
|
-
// TypeScript / Vitest
|
|
133
|
-
test('calculateTotal with discount applies percentage', () => {
|
|
134
|
-
// Arrange
|
|
135
|
-
const cart = new Cart([{ price: 100 }, { price: 50 }]);
|
|
136
|
-
const discount = new PercentageDiscount(10);
|
|
137
|
-
|
|
138
|
-
// Act
|
|
139
|
-
const total = cart.calculateTotal(discount);
|
|
140
|
-
|
|
141
|
-
// Assert
|
|
142
|
-
expect(total).toBe(135.0);
|
|
143
|
-
});
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
```go
|
|
147
|
-
// Go / testing
|
|
148
|
-
func TestCalculateTotal_WithDiscount_AppliesPercentage(t *testing.T) {
|
|
149
|
-
// Arrange
|
|
150
|
-
cart := NewCart([]Item{{Price: 100}, {Price: 50}})
|
|
151
|
-
discount := NewPercentageDiscount(10)
|
|
152
|
-
|
|
153
|
-
// Act
|
|
154
|
-
total := cart.CalculateTotal(discount)
|
|
155
|
-
|
|
156
|
-
// Assert
|
|
157
|
-
assert.Equal(t, 135.0, total)
|
|
158
|
-
}
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
### Test Naming
|
|
162
|
-
|
|
163
|
-
Use the language-idiomatic convention:
|
|
164
|
-
|
|
165
|
-
| Language | Convention | Example |
|
|
166
|
-
|----------|-----------|---------|
|
|
167
|
-
| Python | `test_method_scenario_expected` | `test_get_user_with_invalid_id_raises_not_found` |
|
|
168
|
-
| JS/TS | descriptive string | `'getUser with invalid id throws NotFound'` |
|
|
169
|
-
| Go | `TestMethod_Scenario_Expected` | `TestGetUser_WithInvalidId_ReturnsNotFound` |
|
|
170
|
-
| Rust | `test_method_scenario_expected` | `test_get_user_with_invalid_id_returns_not_found` |
|
|
171
|
-
| Java/C# | `MethodName_Scenario_ExpectedBehavior` | `GetUser_WithInvalidId_ThrowsNotFoundException` |
|
|
172
|
-
| Ruby | descriptive string (RSpec) | `'raises NotFound for invalid id'` |
|
|
173
|
-
|
|
174
|
-
### Test Isolation
|
|
175
|
-
|
|
176
|
-
- Each test must be independent — no shared mutable state
|
|
177
|
-
- Use setup/teardown (beforeEach, setUp, constructor) for fresh fixtures
|
|
178
|
-
- Tests must pass in any order and in parallel
|
|
179
|
-
- Never depend on external services, file system, or network in unit tests
|
|
180
|
-
|
|
181
|
-
### Mocking Boundaries
|
|
182
|
-
|
|
183
|
-
- Mock/stub at system boundaries only (databases, APIs, file system, clock)
|
|
184
|
-
- Do NOT mock the class under test
|
|
185
|
-
- Do NOT mock value objects or simple data structures
|
|
186
|
-
- Prefer fakes/stubs over mocks when possible — verify state, not interactions
|
|
187
|
-
- Use dependency injection to make code testable
|
|
188
|
-
|
|
189
|
-
### One Assertion Focus
|
|
190
|
-
|
|
191
|
-
Each test should verify ONE logical concept. Multiple `assert` calls are fine if they verify aspects of the same behavior:
|
|
192
|
-
|
|
193
|
-
```python
|
|
194
|
-
# Good — one concept (successful creation), multiple assertions
|
|
195
|
-
def test_create_user_with_valid_data_returns_user():
|
|
196
|
-
user = create_user(name="Alice", email="alice@example.com")
|
|
197
|
-
assert user.name == "Alice"
|
|
198
|
-
assert user.email == "alice@example.com"
|
|
199
|
-
assert user.id is not None
|
|
200
|
-
|
|
201
|
-
# Bad — testing two unrelated behaviors in one test
|
|
202
|
-
def test_user_creation_and_deletion():
|
|
203
|
-
user = create_user(name="Alice")
|
|
204
|
-
assert user.id is not None
|
|
205
|
-
delete_user(user.id)
|
|
206
|
-
assert get_user(user.id) is None # This is a separate test
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
### Test Behavior, Not Implementation
|
|
210
|
-
|
|
211
|
-
Tests should verify WHAT the code does, not HOW it does it:
|
|
212
|
-
|
|
213
|
-
```typescript
|
|
214
|
-
// Good — tests the result
|
|
215
|
-
expect(sort([3, 1, 2])).toEqual([1, 2, 3]);
|
|
216
|
-
|
|
217
|
-
// Bad — tests that a specific algorithm was used
|
|
218
|
-
expect(quickSort).toHaveBeenCalledWith([3, 1, 2]);
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
## Constraints
|
|
222
|
-
|
|
223
|
-
### ALWAYS
|
|
224
|
-
|
|
225
|
-
- ALWAYS detect language and framework before writing tests
|
|
226
|
-
- ALWAYS check for existing test files and match their conventions
|
|
227
|
-
- ALWAYS present test plan as method names ONLY before writing
|
|
228
|
-
- ALWAYS ask for explicit approval: "Do you approve this test plan?"
|
|
229
|
-
- ALWAYS use AAA pattern with section comments
|
|
230
|
-
- ALWAYS use language-idiomatic naming conventions
|
|
231
|
-
- ALWAYS isolate tests — no shared mutable state between tests
|
|
232
|
-
|
|
233
|
-
### NEVER
|
|
234
|
-
|
|
235
|
-
- NEVER write test implementations until user explicitly approves the plan
|
|
236
|
-
- NEVER create production code — only test code
|
|
237
|
-
- NEVER mock the class/module under test
|
|
238
|
-
- NEVER write tests that depend on execution order
|
|
239
|
-
- NEVER use real external services (databases, APIs) in unit tests
|
|
240
|
-
- NEVER ignore existing project test conventions
|
|
241
|
-
|
|
242
|
-
## Reference Materials
|
|
243
|
-
|
|
244
|
-
For detailed guidance on specific topics:
|
|
245
|
-
|
|
246
|
-
- **[Language Frameworks](reference/language-frameworks.md)** — Framework-specific patterns, assertions, and setup for each language
|
|
247
|
-
- **[Anti-Patterns](reference/anti-patterns.md)** — Common testing mistakes and how to fix them
|
|
248
|
-
- **[TDD Workflow Patterns](reference/tdd-workflow-patterns.md)** — Red-Green-Refactor, Transformation Priority Premise, when to use TDD
|
|
@@ -1,388 +0,0 @@
|
|
|
1
|
-
# Language & Framework Reference
|
|
2
|
-
|
|
3
|
-
Quick-reference for each language's test ecosystem. Use this to apply framework-specific patterns after detecting the project's language.
|
|
4
|
-
|
|
5
|
-
## Table of Contents
|
|
6
|
-
|
|
7
|
-
- [JavaScript / TypeScript](#javascript--typescript)
|
|
8
|
-
- [Python](#python)
|
|
9
|
-
- [Go](#go)
|
|
10
|
-
- [Rust](#rust)
|
|
11
|
-
- [Java / Kotlin](#java--kotlin)
|
|
12
|
-
- [C# / .NET](#c--net)
|
|
13
|
-
- [Ruby](#ruby)
|
|
14
|
-
|
|
15
|
-
## JavaScript / TypeScript
|
|
16
|
-
|
|
17
|
-
### Frameworks
|
|
18
|
-
|
|
19
|
-
| Framework | When to Use | Key Feature |
|
|
20
|
-
|-----------|-------------|-------------|
|
|
21
|
-
| **Vitest** | Vite projects, new TS projects | Fast, ESM-native, Jest-compatible API |
|
|
22
|
-
| **Jest** | React (CRA), existing Jest projects | Mature ecosystem, wide adoption |
|
|
23
|
-
| **Mocha + Chai** | Legacy projects, custom setups | Flexible, pluggable |
|
|
24
|
-
| **Node test runner** | Node.js 20+, minimal deps | Built-in, zero dependencies |
|
|
25
|
-
|
|
26
|
-
### Vitest / Jest Patterns
|
|
27
|
-
|
|
28
|
-
```typescript
|
|
29
|
-
import { describe, test, expect, vi, beforeEach } from 'vitest';
|
|
30
|
-
|
|
31
|
-
describe('OrderService', () => {
|
|
32
|
-
let mockRepo: MockedObject<OrderRepository>;
|
|
33
|
-
let service: OrderService;
|
|
34
|
-
|
|
35
|
-
beforeEach(() => {
|
|
36
|
-
mockRepo = { save: vi.fn(), findById: vi.fn() };
|
|
37
|
-
service = new OrderService(mockRepo);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
test('processOrder with valid order saves and returns id', async () => {
|
|
41
|
-
// Arrange
|
|
42
|
-
const order = createValidOrder();
|
|
43
|
-
mockRepo.save.mockResolvedValue({ id: '123' });
|
|
44
|
-
|
|
45
|
-
// Act
|
|
46
|
-
const result = await service.processOrder(order);
|
|
47
|
-
|
|
48
|
-
// Assert
|
|
49
|
-
expect(result.id).toBe('123');
|
|
50
|
-
expect(mockRepo.save).toHaveBeenCalledWith(order);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test('processOrder with invalid order throws ValidationError', async () => {
|
|
54
|
-
// Arrange
|
|
55
|
-
const order = createInvalidOrder();
|
|
56
|
-
|
|
57
|
-
// Act & Assert
|
|
58
|
-
await expect(service.processOrder(order)).rejects.toThrow(ValidationError);
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
### File Conventions
|
|
64
|
-
|
|
65
|
-
- `*.test.ts` / `*.spec.ts` (co-located or in `__tests__/`)
|
|
66
|
-
- `vitest.config.ts` or `jest.config.ts` for configuration
|
|
67
|
-
|
|
68
|
-
## Python
|
|
69
|
-
|
|
70
|
-
### Frameworks
|
|
71
|
-
|
|
72
|
-
| Framework | When to Use | Key Feature |
|
|
73
|
-
|-----------|-------------|-------------|
|
|
74
|
-
| **pytest** | Default choice, most projects | Fixtures, parametrize, plugins |
|
|
75
|
-
| **unittest** | stdlib only, legacy projects | Built-in, class-based |
|
|
76
|
-
|
|
77
|
-
### pytest Patterns
|
|
78
|
-
|
|
79
|
-
```python
|
|
80
|
-
import pytest
|
|
81
|
-
from unittest.mock import Mock, AsyncMock, patch
|
|
82
|
-
|
|
83
|
-
@pytest.fixture
|
|
84
|
-
def mock_repo():
|
|
85
|
-
repo = Mock(spec=OrderRepository)
|
|
86
|
-
repo.save = AsyncMock(return_value=Order(id="123"))
|
|
87
|
-
return repo
|
|
88
|
-
|
|
89
|
-
@pytest.fixture
|
|
90
|
-
def service(mock_repo):
|
|
91
|
-
return OrderService(repository=mock_repo)
|
|
92
|
-
|
|
93
|
-
async def test_process_order_with_valid_order_returns_id(service, mock_repo):
|
|
94
|
-
# Arrange
|
|
95
|
-
order = create_valid_order()
|
|
96
|
-
|
|
97
|
-
# Act
|
|
98
|
-
result = await service.process_order(order)
|
|
99
|
-
|
|
100
|
-
# Assert
|
|
101
|
-
assert result.id == "123"
|
|
102
|
-
mock_repo.save.assert_called_once_with(order)
|
|
103
|
-
|
|
104
|
-
async def test_process_order_with_invalid_order_raises_validation_error(service):
|
|
105
|
-
# Arrange
|
|
106
|
-
order = create_invalid_order()
|
|
107
|
-
|
|
108
|
-
# Act & Assert
|
|
109
|
-
with pytest.raises(ValidationError, match="items cannot be empty"):
|
|
110
|
-
await service.process_order(order)
|
|
111
|
-
|
|
112
|
-
@pytest.mark.parametrize("discount,expected", [
|
|
113
|
-
(0, 100.0),
|
|
114
|
-
(10, 90.0),
|
|
115
|
-
(50, 50.0),
|
|
116
|
-
])
|
|
117
|
-
def test_apply_discount_calculates_correctly(discount, expected):
|
|
118
|
-
assert apply_discount(100.0, discount) == expected
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
### File Conventions
|
|
122
|
-
|
|
123
|
-
- `test_*.py` or `*_test.py` in `tests/` directory
|
|
124
|
-
- `conftest.py` for shared fixtures
|
|
125
|
-
- `pytest.ini` or `[tool.pytest.ini_options]` in `pyproject.toml`
|
|
126
|
-
|
|
127
|
-
## Go
|
|
128
|
-
|
|
129
|
-
### Frameworks
|
|
130
|
-
|
|
131
|
-
| Framework | When to Use | Key Feature |
|
|
132
|
-
|-----------|-------------|-------------|
|
|
133
|
-
| **testing** (stdlib) | Always available | Built-in, no dependencies |
|
|
134
|
-
| **testify** | Assertions + mocking | `assert`, `require`, `mock` packages |
|
|
135
|
-
| **gomock** | Interface mocking | Code generation, strict expectations |
|
|
136
|
-
|
|
137
|
-
### Go Patterns
|
|
138
|
-
|
|
139
|
-
```go
|
|
140
|
-
func TestProcessOrder_WithValidOrder_ReturnsID(t *testing.T) {
|
|
141
|
-
// Arrange
|
|
142
|
-
repo := new(MockOrderRepository)
|
|
143
|
-
repo.On("Save", mock.Anything).Return(&Order{ID: "123"}, nil)
|
|
144
|
-
service := NewOrderService(repo)
|
|
145
|
-
|
|
146
|
-
// Act
|
|
147
|
-
result, err := service.ProcessOrder(context.Background(), validOrder)
|
|
148
|
-
|
|
149
|
-
// Assert
|
|
150
|
-
require.NoError(t, err)
|
|
151
|
-
assert.Equal(t, "123", result.ID)
|
|
152
|
-
repo.AssertExpectations(t)
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
func TestProcessOrder_WithInvalidOrder_ReturnsError(t *testing.T) {
|
|
156
|
-
// Arrange
|
|
157
|
-
service := NewOrderService(nil)
|
|
158
|
-
|
|
159
|
-
// Act
|
|
160
|
-
_, err := service.ProcessOrder(context.Background(), invalidOrder)
|
|
161
|
-
|
|
162
|
-
// Assert
|
|
163
|
-
assert.ErrorIs(t, err, ErrValidation)
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Table-driven tests (Go idiom)
|
|
167
|
-
func TestApplyDiscount(t *testing.T) {
|
|
168
|
-
tests := []struct {
|
|
169
|
-
name string
|
|
170
|
-
price float64
|
|
171
|
-
discount int
|
|
172
|
-
expected float64
|
|
173
|
-
}{
|
|
174
|
-
{"no discount", 100.0, 0, 100.0},
|
|
175
|
-
{"10 percent", 100.0, 10, 90.0},
|
|
176
|
-
{"50 percent", 100.0, 50, 50.0},
|
|
177
|
-
}
|
|
178
|
-
for _, tt := range tests {
|
|
179
|
-
t.Run(tt.name, func(t *testing.T) {
|
|
180
|
-
result := ApplyDiscount(tt.price, tt.discount)
|
|
181
|
-
assert.Equal(t, tt.expected, result)
|
|
182
|
-
})
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
### File Conventions
|
|
188
|
-
|
|
189
|
-
- `*_test.go` in the same package (or `_test` package for black-box)
|
|
190
|
-
- `go test ./...` to run all tests
|
|
191
|
-
- `testdata/` for test fixtures
|
|
192
|
-
|
|
193
|
-
## Rust
|
|
194
|
-
|
|
195
|
-
### Patterns
|
|
196
|
-
|
|
197
|
-
```rust
|
|
198
|
-
#[cfg(test)]
|
|
199
|
-
mod tests {
|
|
200
|
-
use super::*;
|
|
201
|
-
use mockall::predicate::*;
|
|
202
|
-
|
|
203
|
-
#[test]
|
|
204
|
-
fn process_order_with_valid_order_returns_id() {
|
|
205
|
-
// Arrange
|
|
206
|
-
let mut mock_repo = MockOrderRepository::new();
|
|
207
|
-
mock_repo.expect_save()
|
|
208
|
-
.returning(|_| Ok(Order { id: "123".into() }));
|
|
209
|
-
let service = OrderService::new(Box::new(mock_repo));
|
|
210
|
-
|
|
211
|
-
// Act
|
|
212
|
-
let result = service.process_order(&valid_order());
|
|
213
|
-
|
|
214
|
-
// Assert
|
|
215
|
-
assert_eq!(result.unwrap().id, "123");
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
#[test]
|
|
219
|
-
fn process_order_with_invalid_order_returns_error() {
|
|
220
|
-
// Arrange
|
|
221
|
-
let service = OrderService::new(Box::new(MockOrderRepository::new()));
|
|
222
|
-
|
|
223
|
-
// Act
|
|
224
|
-
let result = service.process_order(&invalid_order());
|
|
225
|
-
|
|
226
|
-
// Assert
|
|
227
|
-
assert!(matches!(result, Err(ServiceError::Validation(_))));
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
### File Conventions
|
|
233
|
-
|
|
234
|
-
- Tests in `#[cfg(test)] mod tests` at bottom of source file (unit tests)
|
|
235
|
-
- Integration tests in `tests/` directory
|
|
236
|
-
- `cargo test` to run all
|
|
237
|
-
|
|
238
|
-
## Java / Kotlin
|
|
239
|
-
|
|
240
|
-
### Frameworks
|
|
241
|
-
|
|
242
|
-
| Framework | When to Use | Key Feature |
|
|
243
|
-
|-----------|-------------|-------------|
|
|
244
|
-
| **JUnit 5** | Default choice | Modern, extensible, parameterized |
|
|
245
|
-
| **Mockito** | Mocking | Intuitive API, wide adoption |
|
|
246
|
-
| **AssertJ** | Fluent assertions | Readable, discoverable API |
|
|
247
|
-
|
|
248
|
-
### JUnit 5 + Mockito Patterns
|
|
249
|
-
|
|
250
|
-
```java
|
|
251
|
-
@ExtendWith(MockitoExtension.class)
|
|
252
|
-
class OrderServiceTest {
|
|
253
|
-
@Mock OrderRepository repository;
|
|
254
|
-
@InjectMocks OrderService service;
|
|
255
|
-
|
|
256
|
-
@Test
|
|
257
|
-
void processOrder_withValidOrder_returnsId() {
|
|
258
|
-
// Arrange
|
|
259
|
-
var order = createValidOrder();
|
|
260
|
-
when(repository.save(any())).thenReturn(new Order("123"));
|
|
261
|
-
|
|
262
|
-
// Act
|
|
263
|
-
var result = service.processOrder(order);
|
|
264
|
-
|
|
265
|
-
// Assert
|
|
266
|
-
assertThat(result.getId()).isEqualTo("123");
|
|
267
|
-
verify(repository).save(order);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
@Test
|
|
271
|
-
void processOrder_withInvalidOrder_throwsValidationException() {
|
|
272
|
-
// Arrange
|
|
273
|
-
var order = createInvalidOrder();
|
|
274
|
-
|
|
275
|
-
// Act & Assert
|
|
276
|
-
assertThatThrownBy(() -> service.processOrder(order))
|
|
277
|
-
.isInstanceOf(ValidationException.class)
|
|
278
|
-
.hasMessageContaining("items cannot be empty");
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
@ParameterizedTest
|
|
282
|
-
@CsvSource({"0, 100.0", "10, 90.0", "50, 50.0"})
|
|
283
|
-
void applyDiscount_calculatesCorrectly(int discount, double expected) {
|
|
284
|
-
assertThat(applyDiscount(100.0, discount)).isEqualTo(expected);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
### File Conventions
|
|
290
|
-
|
|
291
|
-
- `src/test/java/` mirroring `src/main/java/` structure
|
|
292
|
-
- `*Test.java` suffix
|
|
293
|
-
- `mvn test` or `gradle test`
|
|
294
|
-
|
|
295
|
-
## C# / .NET
|
|
296
|
-
|
|
297
|
-
**If the `grimoire.dotnet-unit-testing` skill is available, defer to it for full C#/.NET guidance.** It provides comprehensive xUnit, TUnit, Moq, and NSubstitute patterns.
|
|
298
|
-
|
|
299
|
-
### Quick Reference (when dotnet skill is unavailable)
|
|
300
|
-
|
|
301
|
-
| Framework | When to Use |
|
|
302
|
-
|-----------|-------------|
|
|
303
|
-
| **xUnit** | Default, most universal |
|
|
304
|
-
| **NUnit** | If project already uses it |
|
|
305
|
-
| **MSTest** | Microsoft-first shops |
|
|
306
|
-
|
|
307
|
-
```csharp
|
|
308
|
-
public class OrderServiceTests
|
|
309
|
-
{
|
|
310
|
-
private readonly Mock<IOrderRepository> _mockRepo;
|
|
311
|
-
private readonly OrderService _sut;
|
|
312
|
-
|
|
313
|
-
public OrderServiceTests()
|
|
314
|
-
{
|
|
315
|
-
_mockRepo = new Mock<IOrderRepository>();
|
|
316
|
-
_sut = new OrderService(_mockRepo.Object);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
[Fact]
|
|
320
|
-
public async Task ProcessOrder_WithValidOrder_ReturnsId()
|
|
321
|
-
{
|
|
322
|
-
// Arrange
|
|
323
|
-
var order = CreateValidOrder();
|
|
324
|
-
_mockRepo.Setup(r => r.SaveAsync(It.IsAny<Order>()))
|
|
325
|
-
.ReturnsAsync(new Order { Id = "123" });
|
|
326
|
-
|
|
327
|
-
// Act
|
|
328
|
-
var result = await _sut.ProcessOrderAsync(order);
|
|
329
|
-
|
|
330
|
-
// Assert
|
|
331
|
-
Assert.Equal("123", result.Id);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
### File Conventions
|
|
337
|
-
|
|
338
|
-
- `Tests/` or `*.Tests/` project mirroring source structure
|
|
339
|
-
- `*Tests.cs` suffix
|
|
340
|
-
- `dotnet test` to run
|
|
341
|
-
|
|
342
|
-
## Ruby
|
|
343
|
-
|
|
344
|
-
### Frameworks
|
|
345
|
-
|
|
346
|
-
| Framework | When to Use |
|
|
347
|
-
|-----------|-------------|
|
|
348
|
-
| **RSpec** | Default, most Ruby projects |
|
|
349
|
-
| **Minitest** | stdlib, Rails default |
|
|
350
|
-
|
|
351
|
-
### RSpec Patterns
|
|
352
|
-
|
|
353
|
-
```ruby
|
|
354
|
-
RSpec.describe OrderService do
|
|
355
|
-
let(:repository) { instance_double(OrderRepository) }
|
|
356
|
-
let(:service) { described_class.new(repository: repository) }
|
|
357
|
-
|
|
358
|
-
describe '#process_order' do
|
|
359
|
-
context 'with a valid order' do
|
|
360
|
-
it 'returns the order id' do
|
|
361
|
-
# Arrange
|
|
362
|
-
order = build(:order, :valid)
|
|
363
|
-
allow(repository).to receive(:save).and_return(Order.new(id: '123'))
|
|
364
|
-
|
|
365
|
-
# Act
|
|
366
|
-
result = service.process_order(order)
|
|
367
|
-
|
|
368
|
-
# Assert
|
|
369
|
-
expect(result.id).to eq('123')
|
|
370
|
-
expect(repository).to have_received(:save).with(order)
|
|
371
|
-
end
|
|
372
|
-
end
|
|
373
|
-
|
|
374
|
-
context 'with an invalid order' do
|
|
375
|
-
it 'raises ValidationError' do
|
|
376
|
-
order = build(:order, :invalid)
|
|
377
|
-
expect { service.process_order(order) }.to raise_error(ValidationError)
|
|
378
|
-
end
|
|
379
|
-
end
|
|
380
|
-
end
|
|
381
|
-
end
|
|
382
|
-
```
|
|
383
|
-
|
|
384
|
-
### File Conventions
|
|
385
|
-
|
|
386
|
-
- `spec/` directory mirroring `lib/` or `app/` structure
|
|
387
|
-
- `*_spec.rb` suffix
|
|
388
|
-
- `bundle exec rspec` to run
|