@grimoire-cc/cli 0.13.2 → 0.14.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.
Files changed (50) hide show
  1. package/dist/commands/update.d.ts.map +1 -1
  2. package/dist/commands/update.js +14 -0
  3. package/dist/commands/update.js.map +1 -1
  4. package/dist/enforce.d.ts +3 -1
  5. package/dist/enforce.d.ts.map +1 -1
  6. package/dist/enforce.js +18 -6
  7. package/dist/enforce.js.map +1 -1
  8. package/dist/setup.d.ts.map +1 -1
  9. package/dist/setup.js +47 -0
  10. package/dist/setup.js.map +1 -1
  11. package/dist/summary.d.ts.map +1 -1
  12. package/dist/summary.js +9 -0
  13. package/dist/summary.js.map +1 -1
  14. package/package.json +1 -1
  15. package/packs/dev-pack/agents/grimoire.tdd-specialist.md +194 -27
  16. package/packs/dev-pack/grimoire.json +9 -42
  17. package/packs/dev-pack/skills/grimoire.conventional-commit/SKILL.md +68 -47
  18. package/packs/dotnet-pack/agents/grimoire.csharp-coder.md +110 -113
  19. package/packs/dotnet-pack/grimoire.json +23 -5
  20. package/packs/dotnet-pack/skills/grimoire.unit-testing-dotnet/SKILL.md +252 -0
  21. package/packs/{dev-pack/skills/grimoire.tdd-specialist → dotnet-pack/skills/grimoire.unit-testing-dotnet}/reference/anti-patterns.md +78 -0
  22. package/packs/dotnet-pack/skills/grimoire.unit-testing-dotnet/reference/tdd-workflow-patterns.md +259 -0
  23. package/packs/go-pack/grimoire.json +19 -0
  24. package/packs/go-pack/skills/grimoire.unit-testing-go/SKILL.md +256 -0
  25. package/packs/go-pack/skills/grimoire.unit-testing-go/reference/anti-patterns.md +244 -0
  26. package/packs/go-pack/skills/grimoire.unit-testing-go/reference/tdd-workflow-patterns.md +259 -0
  27. package/packs/python-pack/grimoire.json +19 -0
  28. package/packs/python-pack/skills/grimoire.unit-testing-python/SKILL.md +239 -0
  29. package/packs/python-pack/skills/grimoire.unit-testing-python/reference/anti-patterns.md +244 -0
  30. package/packs/python-pack/skills/grimoire.unit-testing-python/reference/tdd-workflow-patterns.md +259 -0
  31. package/packs/rust-pack/grimoire.json +29 -0
  32. package/packs/rust-pack/skills/grimoire.unit-testing-rust/SKILL.md +243 -0
  33. package/packs/rust-pack/skills/grimoire.unit-testing-rust/reference/anti-patterns.md +244 -0
  34. package/packs/rust-pack/skills/grimoire.unit-testing-rust/reference/tdd-workflow-patterns.md +259 -0
  35. package/packs/ts-pack/agents/grimoire.typescript-coder.md +36 -1
  36. package/packs/ts-pack/grimoire.json +27 -1
  37. package/packs/ts-pack/skills/grimoire.unit-testing-typescript/SKILL.md +255 -0
  38. package/packs/ts-pack/skills/grimoire.unit-testing-typescript/reference/anti-patterns.md +244 -0
  39. package/packs/ts-pack/skills/grimoire.unit-testing-typescript/reference/tdd-workflow-patterns.md +259 -0
  40. package/packs/dev-pack/skills/grimoire.tdd-specialist/SKILL.md +0 -248
  41. package/packs/dev-pack/skills/grimoire.tdd-specialist/reference/language-frameworks.md +0 -388
  42. package/packs/dev-pack/skills/grimoire.tdd-specialist/reference/tdd-workflow-patterns.md +0 -135
  43. package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/SKILL.md +0 -293
  44. package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/anti-patterns.md +0 -329
  45. package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/framework-guidelines.md +0 -361
  46. package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/parameterized-testing.md +0 -378
  47. package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/test-organization.md +0 -476
  48. package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/test-performance.md +0 -576
  49. package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/templates/tunit-template.md +0 -438
  50. package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/templates/xunit-template.md +0 -303
@@ -0,0 +1,252 @@
1
+ ---
2
+ name: grimoire.unit-testing-dotnet
3
+ description: "C#/.NET unit testing specialist. Framework selection, patterns, and best practices for xUnit, TUnit, NUnit, Moq, and NSubstitute. Use when writing tests for .cs files, configuring test projects, or asking about .NET testing patterns, mocking, assertions, async testing, FluentAssertions alternatives."
4
+ ---
5
+
6
+ # .NET Unit Testing
7
+
8
+ Expert guidance for writing clean, maintainable unit tests in C#/.NET projects.
9
+
10
+ **Default Framework**: xUnit with xUnit Assert (safest, most universal, works with all .NET versions)
11
+ **Recommended for new .NET 8+ projects**: TUnit (modern, async-first, built-in fluent assertions, MIT license)
12
+
13
+ ## Framework Selection
14
+
15
+ ### Detection
16
+
17
+ 1. Check existing test files first — always match what the project uses
18
+ 2. Check `.csproj` for `TargetFramework` and test package references
19
+ 3. Check for xUnit (`xunit`), TUnit (`TUnit`), NUnit (`NUnit`), MSTest (`MSTest.TestFramework`)
20
+
21
+ ### Decision Table
22
+
23
+ | Condition | Use | Reason |
24
+ |-----------|-----|--------|
25
+ | Project has existing tests | **Match existing** | Consistency is paramount |
26
+ | New .NET 8+ greenfield | **Offer TUnit** | Modern, async-first, built-in assertions |
27
+ | New .NET 6/7 project | **xUnit** | TUnit requires .NET 8+ |
28
+ | .NET Framework project | **xUnit** | Universal compatibility |
29
+ | Project uses NUnit | **NUnit** | Match existing |
30
+ | Uncertain or mixed | **xUnit** | Safe default |
31
+
32
+ **For new .NET 8+ projects without existing tests:**
33
+ Offer the choice: "This is a new .NET 8+ project. I'll use **xUnit** (industry standard) by default. Would you prefer **TUnit** instead? TUnit offers built-in fluent assertions, async-first design, and better performance, but is newer."
34
+
35
+ **Note on FluentAssertions**: Version 8+ requires a commercial license ($130/dev/year). Avoid recommending it unless the project already uses it.
36
+
37
+ ## Naming Conventions
38
+
39
+ Use `MethodName_Scenario_ExpectedBehavior` with PascalCase:
40
+
41
+ ```csharp
42
+ // Pattern: MethodName_Scenario_ExpectedBehavior
43
+ ProcessOrder_WithValidOrder_ReturnsSuccess()
44
+ GetUser_WithNonExistentId_ThrowsUserNotFoundException()
45
+ CalculateDiscount_WhenOrderExceeds100_Returns10PercentOff()
46
+ ```
47
+
48
+ ## Patterns
49
+
50
+ ### AAA with xUnit
51
+
52
+ ```csharp
53
+ public class OrderServiceTests : IDisposable
54
+ {
55
+ private readonly Mock<IOrderRepository> _mockRepo;
56
+ private readonly FakeLogger<OrderService> _fakeLogger;
57
+ private readonly OrderService _sut;
58
+
59
+ public OrderServiceTests()
60
+ {
61
+ _mockRepo = new Mock<IOrderRepository>();
62
+ _fakeLogger = new FakeLogger<OrderService>();
63
+ _sut = new OrderService(_fakeLogger, _mockRepo.Object);
64
+ }
65
+
66
+ [Fact]
67
+ public async Task ProcessOrder_WithValidOrder_ReturnsSuccess()
68
+ {
69
+ // Arrange
70
+ var order = CreateValidOrder();
71
+ _mockRepo.Setup(r => r.SaveAsync(It.IsAny<Order>()))
72
+ .ReturnsAsync(new Order { Id = "123" });
73
+
74
+ // Act
75
+ var result = await _sut.ProcessOrderAsync(order);
76
+
77
+ // Assert
78
+ Assert.True(result.IsSuccess);
79
+ Assert.Equal("123", result.Id);
80
+ }
81
+
82
+ public void Dispose() { /* cleanup if needed */ }
83
+ }
84
+ ```
85
+
86
+ ### AAA with TUnit
87
+
88
+ ```csharp
89
+ public class OrderServiceTests
90
+ {
91
+ private readonly Mock<IOrderRepository> _mockRepo = new();
92
+ private readonly OrderService _sut;
93
+
94
+ public OrderServiceTests()
95
+ {
96
+ _sut = new OrderService(_mockRepo.Object);
97
+ }
98
+
99
+ [Test]
100
+ public async Task ProcessOrder_WithValidOrder_ReturnsSuccess()
101
+ {
102
+ // Arrange
103
+ var order = CreateValidOrder();
104
+ _mockRepo.Setup(r => r.SaveAsync(It.IsAny<Order>()))
105
+ .ReturnsAsync(new Order { Id = "123" });
106
+
107
+ // Act
108
+ var result = await _sut.ProcessOrderAsync(order);
109
+
110
+ // Assert — TUnit assertions are async and fluent
111
+ await Assert.That(result.IsSuccess).IsTrue();
112
+ await Assert.That(result.Id).IsEqualTo("123");
113
+ }
114
+ }
115
+ ```
116
+
117
+ ### Parameterized Tests
118
+
119
+ ```csharp
120
+ // xUnit
121
+ [Theory]
122
+ [InlineData(0, 100.0)]
123
+ [InlineData(10, 90.0)]
124
+ [InlineData(50, 50.0)]
125
+ public void ApplyDiscount_CalculatesCorrectly(int discount, double expected)
126
+ {
127
+ Assert.Equal(expected, ApplyDiscount(100.0, discount));
128
+ }
129
+
130
+ // TUnit
131
+ [Test]
132
+ [Arguments(0, 100.0)]
133
+ [Arguments(10, 90.0)]
134
+ [Arguments(50, 50.0)]
135
+ public async Task ApplyDiscount_CalculatesCorrectly(int discount, double expected)
136
+ {
137
+ await Assert.That(ApplyDiscount(100.0, discount)).IsEqualTo(expected);
138
+ }
139
+ ```
140
+
141
+ ### Error Testing
142
+
143
+ ```csharp
144
+ // xUnit exception testing
145
+ [Fact]
146
+ public async Task ProcessOrder_WithNullOrder_ThrowsArgumentNullException()
147
+ {
148
+ var exception = await Assert.ThrowsAsync<ArgumentNullException>(
149
+ () => _sut.ProcessOrderAsync(null!));
150
+ Assert.Equal("order", exception.ParamName);
151
+ }
152
+
153
+ // TUnit exception testing
154
+ [Test]
155
+ public async Task ProcessOrder_WithNullOrder_ThrowsArgumentNullException()
156
+ {
157
+ var action = () => _sut.ProcessOrderAsync(null!);
158
+ await Assert.That(action).ThrowsException()
159
+ .OfType<ArgumentNullException>();
160
+ }
161
+ ```
162
+
163
+ ### FakeLogger for Logging Tests
164
+
165
+ ```csharp
166
+ using Microsoft.Extensions.Logging.Testing;
167
+
168
+ var fakeLogger = new FakeLogger<OrderService>();
169
+ var sut = new OrderService(fakeLogger);
170
+ await sut.ProcessOrderAsync(orderId: 123);
171
+
172
+ var logEntry = fakeLogger.Collector.GetSnapshot()
173
+ .Single(r => r.Level == LogLevel.Information);
174
+ var state = logEntry.StructuredState!.ToDictionary(x => x.Key, x => x.Value);
175
+ Assert.Equal("123", state["OrderId"]);
176
+ ```
177
+
178
+ ## Mocking
179
+
180
+ ### Moq (default)
181
+
182
+ ```csharp
183
+ var mockRepo = new Mock<IOrderRepository>();
184
+ mockRepo.Setup(r => r.GetByIdAsync(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
185
+ .ReturnsAsync(expectedDocument);
186
+
187
+ // Verify
188
+ mockRepo.Verify(r => r.SaveAsync(It.IsAny<Order>()), Times.Once);
189
+ ```
190
+
191
+ ### NSubstitute
192
+
193
+ ```csharp
194
+ var repo = Substitute.For<IOrderRepository>();
195
+ repo.GetByIdAsync(Arg.Any<Guid>(), Arg.Any<CancellationToken>())
196
+ .Returns(expectedDocument);
197
+
198
+ // Verify
199
+ await repo.Received(1).SaveAsync(Arg.Any<Order>());
200
+ ```
201
+
202
+ ### What NOT to mock
203
+
204
+ - Value objects, records, DTOs
205
+ - Pure static methods with no side effects
206
+ - The class under test itself
207
+ - Simple data structures
208
+
209
+ Mock only at system boundaries: repositories, external APIs, file system, clock.
210
+
211
+ ## File Conventions
212
+
213
+ - `Tests/` or `*.Tests/` project mirroring source structure
214
+ - `*Tests.cs` suffix for test classes
215
+ - Constructor for per-test setup (xUnit creates new instance per test)
216
+ - `IDisposable` for teardown
217
+ - `dotnet test` to run
218
+
219
+ ## Package Setup
220
+
221
+ ```bash
222
+ # xUnit (default)
223
+ dotnet add package xunit
224
+ dotnet add package xunit.runner.visualstudio
225
+ dotnet add package Microsoft.NET.Test.Sdk
226
+
227
+ # TUnit (for .NET 8+ projects)
228
+ dotnet add package TUnit
229
+
230
+ # Mocking
231
+ dotnet add package Moq
232
+ # or
233
+ dotnet add package NSubstitute
234
+
235
+ # Logging testing
236
+ dotnet add package Microsoft.Extensions.Logging.Testing
237
+ ```
238
+
239
+ ## Authoritative Sources
240
+
241
+ - xUnit: https://xunit.net
242
+ - TUnit: https://github.com/thomhurst/TUnit
243
+ - NUnit: https://nunit.org
244
+ - Moq: https://github.com/moq/moq4
245
+ - NSubstitute: https://nsubstitute.github.io
246
+ - Kent Beck — Canon TDD: https://tidyfirst.substack.com/p/canon-tdd
247
+ - Martin Fowler — Mocks Aren't Stubs: https://martinfowler.com/articles/mocksArentStubs.html
248
+
249
+ ## Reference Materials
250
+
251
+ - **[Anti-Patterns](reference/anti-patterns.md)** — Common testing mistakes and how to fix them
252
+ - **[TDD Workflow Patterns](reference/tdd-workflow-patterns.md)** — Red-Green-Refactor, Transformation Priority Premise, when to use TDD
@@ -12,6 +12,8 @@ Common testing mistakes that reduce test value and increase maintenance cost. Th
12
12
  - [The Mockery](#the-mockery)
13
13
  - [The Inspector](#the-inspector)
14
14
  - [The Flaky Test](#the-flaky-test)
15
+ - [The Cargo Culter](#the-cargo-culter)
16
+ - [The Hard Test](#the-hard-test)
15
17
 
16
18
  ## The Liar
17
19
 
@@ -164,3 +166,79 @@ assertThat(result).isSortedAccordingTo(naturalOrder());
164
166
  - Dependency on test execution order
165
167
 
166
168
  **Fix:** Inject time as a dependency. Use fixed seeds for randomness. Ensure test isolation. Use proper async synchronization.
169
+
170
+ ## The Cargo Culter
171
+
172
+ **What it is:** Writing tests to hit a coverage percentage target rather than to verify behavior. The tests exist to satisfy a metric, not to provide confidence.
173
+
174
+ **How to spot it:**
175
+ - Tests that assert trivially obvious things (e.g., `assert user.name == user.name`)
176
+ - Every private method has a corresponding test accessed via reflection
177
+ - 100% coverage but bugs still escape to production
178
+ - Test suite takes minutes to pass but developers don't trust it
179
+
180
+ **Fix:** Coverage is a diagnostic tool, not a goal. Use it to find untested gaps, not as a number to optimize. High 80s–90% emerges naturally from disciplined TDD. A test that only exists to push coverage up is worse than no test — it adds maintenance cost without adding confidence.
181
+
182
+ ```python
183
+ # Bad — written for coverage, not for confidence
184
+ def test_user_has_name():
185
+ user = User(name="Alice")
186
+ assert user.name is not None # This verifies nothing meaningful
187
+
188
+ # Good — written to verify a business rule
189
+ def test_user_with_empty_name_raises_validation_error():
190
+ with pytest.raises(ValidationError, match="name cannot be empty"):
191
+ User(name="")
192
+ ```
193
+
194
+ > See: https://martinfowler.com/bliki/TestCoverage.html
195
+
196
+ ## The Hard Test
197
+
198
+ **What it is:** Not an anti-pattern in the test itself, but a signal from the test about the production code. When a test is painful, complex, or requires elaborate setup, the production code has a design problem.
199
+
200
+ **How to spot it:**
201
+ - Need to mock 5+ dependencies to test one class
202
+ - Need to access private internals to verify behavior
203
+ - Test requires a complex sequence of operations just to get to the state under test
204
+ - You find yourself thinking "testing this would be too hard"
205
+
206
+ **What it signals:**
207
+ - Too many responsibilities in one class (SRP violation)
208
+ - Hidden dependencies or tight coupling
209
+ - Poor separation of concerns
210
+ - Untestable architecture (e.g., side effects embedded in business logic)
211
+
212
+ **Fix:** Resist the urge to skip the test or work around it with clever mocking. Instead, fix the production code design. Extract classes, inject dependencies, separate concerns. A hard test is a free design review — take the feedback.
213
+
214
+ ```python
215
+ # Hard to test — service does too much
216
+ class OrderService:
217
+ def process(self, order):
218
+ db = Database() # hidden dependency
219
+ email = EmailClient() # hidden dependency
220
+ self._validate(order)
221
+ db.save(order)
222
+ email.send_confirmation(order)
223
+ self._update_inventory(order) # another responsibility
224
+
225
+ # Easy to test — dependencies explicit, concerns separated
226
+ class OrderService:
227
+ def __init__(self, repo: OrderRepository, notifier: Notifier):
228
+ self._repo = repo
229
+ self._notifier = notifier
230
+
231
+ def process(self, order: Order) -> OrderResult:
232
+ self._validate(order)
233
+ saved = self._repo.save(order)
234
+ self._notifier.notify(saved)
235
+ return saved
236
+ ```
237
+
238
+ ---
239
+
240
+ ## Further Reading
241
+
242
+ - xUnit Patterns (Meszaros): http://xunitpatterns.com
243
+ - Codepipes testing anti-patterns: https://blog.codepipes.com/testing/software-testing-antipatterns.html
244
+ - Google SWE Book — Test Doubles: https://abseil.io/resources/swe-book/html/ch13.html
@@ -0,0 +1,259 @@
1
+ # TDD Workflow Patterns
2
+
3
+ Guidance on the test-driven development process, when to apply it, and advanced techniques.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Canon TDD — Start with a Test List](#canon-tdd--start-with-a-test-list)
8
+ - [Red-Green-Refactor](#red-green-refactor)
9
+ - [Transformation Priority Premise](#transformation-priority-premise)
10
+ - [F.I.R.S.T. Principles](#first-principles)
11
+ - [London School vs Detroit School](#london-school-vs-detroit-school)
12
+ - [When to Use TDD](#when-to-use-tdd)
13
+ - [When TDD Is Less Effective](#when-tdd-is-less-effective)
14
+ - [BDD and ATDD Extensions](#bdd-and-atdd-extensions)
15
+ - [Advanced Techniques](#advanced-techniques)
16
+
17
+ ## Canon TDD — Start with a Test List
18
+
19
+ > Source: https://tidyfirst.substack.com/p/canon-tdd
20
+
21
+ Kent Beck's recommended starting point is not a single test but a **test list** — a written enumeration of all behaviors you intend to verify. This separates the creative work (what to test) from the mechanical work (write, make pass, refactor).
22
+
23
+ **Process:**
24
+ 1. Write down all behaviors the code needs — a flat list, not tests
25
+ 2. Pick the simplest item on the list
26
+ 3. Write one failing test for it
27
+ 4. Make it pass with the minimum code
28
+ 5. Refactor
29
+ 6. Cross off the item; repeat
30
+
31
+ **Why test order matters:** Starting with simpler behaviors forces simpler transformations (see TPP below) and lets the design emerge naturally. Jumping to complex cases early leads to over-engineered solutions. The test list keeps you focused and prevents scope creep.
32
+
33
+ ## Red-Green-Refactor
34
+
35
+ > Source: https://martinfowler.com/bliki/TestDrivenDevelopment.html
36
+
37
+ The core TDD cycle, repeated in small increments:
38
+
39
+ ### 1. Red — Write a Failing Test
40
+
41
+ Write the smallest test that describes the next piece of behavior. The test MUST fail before you write any production code. A test that passes immediately provides no confidence.
42
+
43
+ **Rules:**
44
+ - Write only ONE test at a time
45
+ - The test should compile/parse but fail at the assertion
46
+ - If the test passes immediately, it's either trivial or testing existing behavior
47
+
48
+ ### 2. Green — Make It Pass
49
+
50
+ Write the MINIMUM code to make the failing test pass. Do not add extra logic, handle cases not yet tested, or optimize.
51
+
52
+ **Rules:**
53
+ - Write the simplest code that makes the test pass
54
+ - It's OK to hardcode values initially — the next test will force generalization
55
+ - Do not add code for future tests
56
+ - All existing tests must still pass
57
+
58
+ ### 3. Refactor — Clean Up
59
+
60
+ With all tests green, improve the code structure without changing behavior. Tests give you the safety net.
61
+
62
+ **Rules:**
63
+ - No new functionality during refactoring
64
+ - All tests must remain green after each refactoring step
65
+ - Remove duplication, improve naming, extract methods
66
+ - Refactor both production code AND test code
67
+
68
+ ### Cycle Length
69
+
70
+ Each Red-Green-Refactor cycle should take 1–10 minutes. If you're spending more than 10 minutes in the Red or Green phase, the step is too large — break it down.
71
+
72
+ ## Transformation Priority Premise
73
+
74
+ > Source: http://blog.cleancoder.com/uncle-bob/2013/05/27/TheTransformationPriorityPremise.html
75
+
76
+ When going from Red to Green, prefer simpler transformations over complex ones. Listed from simplest to most complex:
77
+
78
+ 1. **Constant** — return a hardcoded value
79
+ 2. **Scalar** — replace constant with a variable
80
+ 3. **Direct** — replace unconditional with conditional (if/else)
81
+ 4. **Collection** — operate on a collection instead of a scalar
82
+ 5. **Iteration** — add a loop
83
+ 6. **Recursion** — add recursive call
84
+ 7. **Assignment** — replace computed value with mutation
85
+
86
+ **Example — building FizzBuzz with TDD:**
87
+
88
+ ```
89
+ Test 1: input 1 → "1" Transformation: Constant
90
+ Test 2: input 2 → "2" Transformation: Scalar (use the input)
91
+ Test 3: input 3 → "Fizz" Transformation: Direct (add if)
92
+ Test 4: input 5 → "Buzz" Transformation: Direct (add another if)
93
+ Test 5: input 15 → "FizzBuzz" Transformation: Direct (add combined if)
94
+ Test 6: input 1-15 → full list Transformation: Iteration (generalize)
95
+ ```
96
+
97
+ By following this priority, you avoid over-engineering early and let the design emerge naturally from the tests.
98
+
99
+ ## F.I.R.S.T. Principles
100
+
101
+ Every unit test must satisfy these five properties:
102
+
103
+ | Principle | Definition | Violation Signal |
104
+ |-----------|------------|-----------------|
105
+ | **Fast** | Runs in milliseconds | Real I/O, network calls, `sleep()` |
106
+ | **Independent** | No dependency on other tests | Shared mutable state, ordered execution |
107
+ | **Repeatable** | Same result every run | System clock, random data without seed, race conditions |
108
+ | **Self-Validating** | Pass or fail without manual interpretation | Tests that print output for a human to read |
109
+ | **Timely** | Written before or alongside production code | Tests added weeks after a feature shipped |
110
+
111
+ F.I.R.S.T. is a diagnostic checklist: if a test violates any property, it will erode team trust and reduce the value of the suite.
112
+
113
+ ## London School vs Detroit School
114
+
115
+ > Source: https://martinfowler.com/articles/mocksArentStubs.html
116
+
117
+ Two schools of TDD with different philosophies on test doubles. Most teams use a hybrid.
118
+
119
+ ### Detroit School (Classicist, Inside-Out)
120
+
121
+ - **Unit definition**: A module of any size — can span multiple classes
122
+ - **Approach**: Bottom-up; start from domain logic, build outward
123
+ - **Test doubles**: Avoid mocks; use real objects when feasible
124
+ - **Verification**: State verification — examine the result after execution
125
+ - **Testing style**: Black-box; test through public API
126
+ - **Refactoring**: Safe — tests aren't coupled to implementation details
127
+ - **Best for**: Building confidence in real interactions; reducing brittleness
128
+
129
+ ### London School (Mockist, Outside-In)
130
+
131
+ - **Unit definition**: A single class in isolation
132
+ - **Approach**: Top-down; start from the API, work inward
133
+ - **Test doubles**: Mock all collaborators
134
+ - **Verification**: Behavior verification — confirm correct method calls occurred
135
+ - **Testing style**: White-box; tests know about internals
136
+ - **Refactoring**: Can be brittle — tests break when implementation changes
137
+ - **Best for**: Designing interactions upfront; driving architecture decisions
138
+
139
+ ### Recommended: Hybrid Approach
140
+
141
+ Apply Detroit discipline as the default — use real objects, verify state. Apply London mocking only at architectural boundaries (external APIs, databases, clocks). Never mock value objects, pure functions, or in-process helpers.
142
+
143
+ The most important rule: if you're mocking to make a test easy to write, that's often a design smell (see The Hard Test in anti-patterns). If you're mocking because the dependency is genuinely external or slow, that's the right use.
144
+
145
+ ## When to Use TDD
146
+
147
+ TDD is most valuable when:
148
+
149
+ - **Business logic** — Complex rules, calculations, state machines. TDD forces you to think through all cases before implementing.
150
+ - **Algorithm development** — Sorting, parsing, validation, transformation logic. Tests serve as a specification.
151
+ - **Bug fixes** — Write a test that reproduces the bug first (Red), then fix it (Green). This prevents regressions.
152
+ - **API/interface design** — Writing tests first helps you design interfaces from the consumer's perspective.
153
+ - **Refactoring** — Ensure tests exist before refactoring. If they don't, write characterization tests first, then refactor.
154
+
155
+ ## When TDD Is Less Effective
156
+
157
+ TDD is not universally optimal. Use judgment:
158
+
159
+ - **UI/visual components** — Layout, styling, animations are hard to express as unit tests. Use visual regression testing or snapshot tests instead.
160
+ - **Exploratory/prototype code** — When you don't know what to build yet, writing tests first slows exploration. Spike first, then write tests.
161
+ - **Thin integration layers** — Simple pass-through code (e.g., a controller that calls a service) may not benefit from test-first approach. Integration tests are more valuable here.
162
+ - **Infrastructure/glue code** — Database migrations, config files, build scripts. Test these with integration or end-to-end tests.
163
+ - **External API wrappers** — Thin clients wrapping external APIs are better tested with integration tests against the real (or sandboxed) API.
164
+
165
+ For these cases, write tests AFTER the implementation (test-last), but still write them.
166
+
167
+ ## BDD and ATDD Extensions
168
+
169
+ ### Behavior-Driven Development (BDD)
170
+
171
+ > Source: https://martinfowler.com/bliki/GivenWhenThen.html
172
+
173
+ BDD extends TDD by using natural language to describe behavior. Useful when tests need to be readable by non-developers.
174
+
175
+ **Given-When-Then** structure:
176
+
177
+ ```gherkin
178
+ Given a cart with items totaling $100
179
+ When a 10% discount is applied
180
+ Then the total should be $90
181
+ ```
182
+
183
+ Maps to test code:
184
+
185
+ ```python
186
+ def test_cart_with_10_percent_discount_totals_90():
187
+ # Given
188
+ cart = Cart(items=[Item(price=100)])
189
+
190
+ # When
191
+ cart.apply_discount(PercentageDiscount(10))
192
+
193
+ # Then
194
+ assert cart.total == 90.0
195
+ ```
196
+
197
+ ### Acceptance TDD (ATDD)
198
+
199
+ Write high-level acceptance tests before implementing a feature. These tests describe the feature from the user's perspective and drive the overall design. Unit tests (via TDD) then drive the implementation of each component.
200
+
201
+ **Flow:**
202
+ 1. Write acceptance test (fails — Red)
203
+ 2. Use TDD to implement components needed to pass it
204
+ 3. Acceptance test passes (Green)
205
+ 4. Refactor
206
+
207
+ ATDD is most valuable for features with clear acceptance criteria and when working with product owners or stakeholders.
208
+
209
+ ## Advanced Techniques
210
+
211
+ ### Property-Based Testing
212
+
213
+ Instead of writing individual input/output pairs, define **properties** that should always hold true and let a framework generate hundreds of test cases automatically.
214
+
215
+ **Best for:** Pure functions, algorithms, data transformations, serialization round-trips.
216
+
217
+ **Tools:**
218
+ - Python: [Hypothesis](https://hypothesis.readthedocs.io)
219
+ - JavaScript/TypeScript: [fast-check](https://fast-check.dev)
220
+ - Go: `testing/quick` (stdlib), [gopter](https://github.com/leanovate/gopter)
221
+ - Rust: [proptest](https://github.com/proptest-rs/proptest)
222
+ - Java: [jqwik](https://jqwik.net)
223
+ - Elixir: [StreamData](https://hexdocs.pm/stream_data)
224
+
225
+ **Example property** (Python/Hypothesis):
226
+ ```python
227
+ from hypothesis import given, strategies as st
228
+
229
+ @given(st.lists(st.integers()))
230
+ def test_sort_is_idempotent(lst):
231
+ assert sorted(sorted(lst)) == sorted(lst)
232
+ ```
233
+
234
+ ### Mutation Testing
235
+
236
+ Mutation testing introduces small code changes (mutations) and checks whether your tests catch them. A test suite that lets mutations survive has gaps in its coverage.
237
+
238
+ **Metric:** Mutation score = % of mutations killed. Target 80%+.
239
+
240
+ **Tools:**
241
+ - JavaScript/TypeScript/C#: [Stryker](https://stryker-mutator.io)
242
+ - Java: [PITest](https://pitest.org)
243
+ - Python: [mutmut](https://mutmut.readthedocs.io)
244
+ - Go: [go-mutesting](https://github.com/zimmski/go-mutesting)
245
+
246
+ Run mutation testing periodically (not on every commit) to identify weak spots in the test suite.
247
+
248
+ ### Contract Testing
249
+
250
+ In microservice or distributed architectures, contract tests verify that services communicate correctly without running full integration tests.
251
+
252
+ **How it works:**
253
+ 1. Consumer defines a contract (expected interactions)
254
+ 2. Provider verifies it can fulfill the contract
255
+ 3. Both test independently — no need to spin up the full system
256
+
257
+ **Tool:** [Pact](https://pact.io) — supports most major languages.
258
+
259
+ Contract tests replace the expensive integration test layer for inter-service communication while still catching breaking API changes early.
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "go-pack",
3
+ "version": "1.0.0",
4
+ "agents": [],
5
+ "skills": [
6
+ {
7
+ "name": "grimoire.unit-testing-go",
8
+ "path": "skills/grimoire.unit-testing-go",
9
+ "description": "Go unit testing specialist. Provides conventions and patterns for the testing package and testify.",
10
+ "version": "1.0.0",
11
+ "triggers": {
12
+ "keywords": ["gotest", "testify", "gomock", "table-driven"],
13
+ "file_extensions": [".go"],
14
+ "patterns": ["write.*test", "add.*test", "create.*test", "go.*test", "test.*coverage"],
15
+ "file_paths": ["**/*_test.go", "**/testdata/**"]
16
+ }
17
+ }
18
+ ]
19
+ }