@comfanion/workflow 4.36.59 → 4.36.60
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/package.json +1 -1
- package/src/build-info.json +2 -2
- package/src/opencode/agents/analyst.md +6 -18
- package/src/opencode/agents/architect.md +5 -21
- package/src/opencode/agents/coder.md +8 -13
- package/src/opencode/agents/crawler.md +38 -38
- package/src/opencode/agents/dev.md +1 -62
- package/src/opencode/agents/pm.md +9 -25
- package/src/opencode/agents/researcher.md +1 -6
- package/src/opencode/agents/reviewer.md +26 -29
- package/src/opencode/commands/dev-story.md +2 -1
- package/src/opencode/skills/acceptance-criteria/SKILL.md +1 -1
- package/src/opencode/skills/archiving/SKILL.md +7 -18
- package/src/opencode/skills/changelog/SKILL.md +1 -1
- package/src/opencode/skills/code-review/SKILL.md +2 -3
- package/src/opencode/skills/coding-standards/SKILL.md +8 -18
- package/src/opencode/skills/dev-story/SKILL.md +69 -543
- package/src/opencode/skills/doc-todo/SKILL.md +22 -313
- package/src/opencode/skills/epic-writing/SKILL.md +1 -1
- package/src/opencode/skills/jira-integration/SKILL.md +1 -1
- package/src/opencode/skills/methodologies/SKILL.md +1 -1
- package/src/opencode/skills/module-documentation/SKILL.md +1 -1
- package/src/opencode/skills/prd-validation/SKILL.md +1 -1
- package/src/opencode/skills/prd-writing/SKILL.md +1 -1
- package/src/opencode/skills/requirements-gathering/SKILL.md +1 -1
- package/src/opencode/skills/requirements-validation/SKILL.md +1 -1
- package/src/opencode/skills/research-methodology/SKILL.md +1 -1
- package/src/opencode/skills/story-writing/SKILL.md +1 -1
- package/src/opencode/skills/test-design/SKILL.md +63 -275
- package/src/opencode/skills/translation/SKILL.md +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: test-design
|
|
3
|
-
description:
|
|
3
|
+
description: Use when designing test strategy, writing unit/integration tests, or improving coverage
|
|
4
4
|
license: MIT
|
|
5
5
|
compatibility: opencode
|
|
6
6
|
metadata:
|
|
@@ -10,314 +10,102 @@ metadata:
|
|
|
10
10
|
|
|
11
11
|
# Test Design Skill
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Principles for writing effective tests.
|
|
14
14
|
|
|
15
|
-
## Test
|
|
15
|
+
## Test Naming
|
|
16
16
|
|
|
17
17
|
```
|
|
18
|
-
|
|
19
|
-
/ \ E2E Tests (few)
|
|
20
|
-
/----\
|
|
21
|
-
/ \ Integration Tests (some)
|
|
22
|
-
/--------\
|
|
23
|
-
/ \ Unit Tests (many)
|
|
24
|
-
/------------\
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## Unit Tests
|
|
28
|
-
|
|
29
|
-
### When to Write
|
|
30
|
-
- Every public function
|
|
31
|
-
- Every method with business logic
|
|
32
|
-
- Edge cases and error paths
|
|
33
|
-
|
|
34
|
-
### Structure (Arrange-Act-Assert)
|
|
18
|
+
Test{Component}_{Method}_{Scenario}_{Expected}
|
|
35
19
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
expected := expectedOutput()
|
|
41
|
-
sut := NewSystemUnderTest()
|
|
42
|
-
|
|
43
|
-
// Act - Execute the function
|
|
44
|
-
result, err := sut.Function(input)
|
|
45
|
-
|
|
46
|
-
// Assert - Verify the outcome
|
|
47
|
-
if err != nil {
|
|
48
|
-
t.Fatalf("unexpected error: %v", err)
|
|
49
|
-
}
|
|
50
|
-
if result != expected {
|
|
51
|
-
t.Errorf("got %v, want %v", result, expected)
|
|
52
|
-
}
|
|
53
|
-
}
|
|
20
|
+
Examples:
|
|
21
|
+
- TestUser_Create_ValidData_Success
|
|
22
|
+
- TestUser_Create_EmptyEmail_ReturnsError
|
|
23
|
+
- TestOrder_Cancel_AlreadyShipped_Fails
|
|
54
24
|
```
|
|
55
25
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
```
|
|
59
|
-
Test{Type}_{Method}_{Scenario}
|
|
26
|
+
## Test Structure (AAA)
|
|
60
27
|
|
|
61
|
-
Examples:
|
|
62
|
-
- TestOffer_UpdatePrice_Success
|
|
63
|
-
- TestOffer_UpdatePrice_NegativePrice_ReturnsError
|
|
64
|
-
- TestOffer_Activate_FromPendingStatus_Success
|
|
65
|
-
- TestOffer_Activate_FromDeclinedStatus_Fails
|
|
66
28
|
```
|
|
29
|
+
// Arrange - Setup
|
|
30
|
+
input := ...
|
|
31
|
+
expected := ...
|
|
67
32
|
|
|
68
|
-
|
|
33
|
+
// Act - Execute
|
|
34
|
+
result := sut.Method(input)
|
|
69
35
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
tests := []struct {
|
|
73
|
-
name string
|
|
74
|
-
offer Offer
|
|
75
|
-
wantErr bool
|
|
76
|
-
}{
|
|
77
|
-
{
|
|
78
|
-
name: "valid offer",
|
|
79
|
-
offer: validOffer(),
|
|
80
|
-
wantErr: false,
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
name: "zero price",
|
|
84
|
-
offer: offerWithZeroPrice(),
|
|
85
|
-
wantErr: true,
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
name: "negative quantity",
|
|
89
|
-
offer: offerWithNegativeQty(),
|
|
90
|
-
wantErr: true,
|
|
91
|
-
},
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
for _, tt := range tests {
|
|
95
|
-
t.Run(tt.name, func(t *testing.T) {
|
|
96
|
-
err := tt.offer.Validate()
|
|
97
|
-
if (err != nil) != tt.wantErr {
|
|
98
|
-
t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)
|
|
99
|
-
}
|
|
100
|
-
})
|
|
101
|
-
}
|
|
102
|
-
}
|
|
36
|
+
// Assert - Verify
|
|
37
|
+
assert(result == expected)
|
|
103
38
|
```
|
|
104
39
|
|
|
105
|
-
##
|
|
106
|
-
|
|
107
|
-
### When to Write
|
|
108
|
-
- Repository implementations
|
|
109
|
-
- API endpoints
|
|
110
|
-
- External service integrations
|
|
111
|
-
- Multi-component workflows
|
|
40
|
+
## Table-Driven Tests
|
|
112
41
|
|
|
113
|
-
|
|
42
|
+
When testing multiple scenarios of same function:
|
|
114
43
|
|
|
115
|
-
```go
|
|
116
|
-
func TestMain(m *testing.M) {
|
|
117
|
-
// Setup
|
|
118
|
-
testDB = setupTestDatabase()
|
|
119
|
-
|
|
120
|
-
// Run tests
|
|
121
|
-
code := m.Run()
|
|
122
|
-
|
|
123
|
-
// Teardown
|
|
124
|
-
teardownTestDatabase(testDB)
|
|
125
|
-
|
|
126
|
-
os.Exit(code)
|
|
127
|
-
}
|
|
128
44
|
```
|
|
45
|
+
tests := []struct {
|
|
46
|
+
name string
|
|
47
|
+
input Input
|
|
48
|
+
expected Output
|
|
49
|
+
wantErr bool
|
|
50
|
+
}{
|
|
51
|
+
{"valid input", validInput(), expectedOutput(), false},
|
|
52
|
+
{"empty input", emptyInput(), nil, true},
|
|
53
|
+
}
|
|
129
54
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
tx := testDB.Begin()
|
|
136
|
-
defer tx.Rollback()
|
|
137
|
-
|
|
138
|
-
repo := NewRepository(tx)
|
|
139
|
-
|
|
140
|
-
// Test code here
|
|
55
|
+
for _, tt := range tests {
|
|
56
|
+
t.Run(tt.name, func(t *testing.T) {
|
|
57
|
+
result, err := sut.Method(tt.input)
|
|
58
|
+
// assert
|
|
59
|
+
})
|
|
141
60
|
}
|
|
142
61
|
```
|
|
143
62
|
|
|
144
|
-
## Test Coverage Requirements
|
|
145
|
-
|
|
146
|
-
| Type | Minimum Coverage |
|
|
147
|
-
|------|-----------------|
|
|
148
|
-
| Domain/Business Logic | 80%+ |
|
|
149
|
-
| Use Cases/Handlers | 70%+ |
|
|
150
|
-
| Repositories | 60%+ |
|
|
151
|
-
| HTTP Handlers | 50%+ |
|
|
152
|
-
|
|
153
63
|
## What to Test
|
|
154
64
|
|
|
155
|
-
|
|
156
|
-
-
|
|
157
|
-
-
|
|
158
|
-
-
|
|
65
|
+
**Always test:**
|
|
66
|
+
- Public API / exported functions
|
|
67
|
+
- Business logic / domain rules
|
|
68
|
+
- Error handling paths
|
|
69
|
+
- Edge cases (empty, nil, zero, max)
|
|
159
70
|
- State transitions
|
|
160
71
|
|
|
161
|
-
|
|
162
|
-
-
|
|
163
|
-
- Error scenarios
|
|
164
|
-
- Validation failures
|
|
165
|
-
- Authorization checks
|
|
166
|
-
|
|
167
|
-
### Infrastructure Layer
|
|
168
|
-
- Repository CRUD operations
|
|
169
|
-
- External service adapters
|
|
170
|
-
- Event publishing/consuming
|
|
171
|
-
|
|
172
|
-
## What NOT to Test
|
|
173
|
-
|
|
174
|
-
- Private functions (test via public interface)
|
|
72
|
+
**Skip testing:**
|
|
73
|
+
- Private implementation details
|
|
175
74
|
- Simple getters/setters
|
|
176
|
-
-
|
|
177
|
-
-
|
|
75
|
+
- Generated code
|
|
76
|
+
- Framework internals
|
|
178
77
|
|
|
179
78
|
## Mocking
|
|
180
79
|
|
|
181
|
-
|
|
182
|
-
- External services
|
|
183
|
-
-
|
|
184
|
-
-
|
|
185
|
-
- Random/non-deterministic operations
|
|
186
|
-
|
|
187
|
-
### Interface-Based Mocking
|
|
188
|
-
|
|
189
|
-
```go
|
|
190
|
-
// Define interface
|
|
191
|
-
type UserRepository interface {
|
|
192
|
-
FindByID(ctx context.Context, id string) (*User, error)
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Create mock
|
|
196
|
-
type MockUserRepository struct {
|
|
197
|
-
FindByIDFunc func(ctx context.Context, id string) (*User, error)
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
func (m *MockUserRepository) FindByID(ctx context.Context, id string) (*User, error) {
|
|
201
|
-
return m.FindByIDFunc(ctx, id)
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Use in test
|
|
205
|
-
func TestUseCase(t *testing.T) {
|
|
206
|
-
mockRepo := &MockUserRepository{
|
|
207
|
-
FindByIDFunc: func(ctx context.Context, id string) (*User, error) {
|
|
208
|
-
return &User{ID: id, Name: "Test"}, nil
|
|
209
|
-
},
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
useCase := NewUseCase(mockRepo)
|
|
213
|
-
// ...
|
|
214
|
-
}
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
## Test Data
|
|
218
|
-
|
|
219
|
-
### Builders
|
|
220
|
-
|
|
221
|
-
```go
|
|
222
|
-
func NewTestOffer() *Offer {
|
|
223
|
-
return &Offer{
|
|
224
|
-
ID: NewOfferID(),
|
|
225
|
-
MerchantID: NewMerchantID(),
|
|
226
|
-
Price: decimal.NewFromInt(100),
|
|
227
|
-
Status: OfferStatusPending,
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
func (o *Offer) WithPrice(price decimal.Decimal) *Offer {
|
|
232
|
-
o.Price = price
|
|
233
|
-
return o
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
func (o *Offer) WithStatus(status OfferStatus) *Offer {
|
|
237
|
-
o.Status = status
|
|
238
|
-
return o
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// Usage
|
|
242
|
-
offer := NewTestOffer().WithPrice(decimal.NewFromInt(200)).WithStatus(OfferStatusActive)
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
## Red-Green-Refactor Cycle
|
|
246
|
-
|
|
247
|
-
### 1. RED - Write Failing Test
|
|
248
|
-
|
|
249
|
-
```go
|
|
250
|
-
func TestOffer_UpdatePrice_Success(t *testing.T) {
|
|
251
|
-
offer := NewTestOffer()
|
|
252
|
-
newPrice := decimal.NewFromInt(150)
|
|
253
|
-
|
|
254
|
-
err := offer.UpdatePrice(newPrice)
|
|
255
|
-
|
|
256
|
-
if err != nil {
|
|
257
|
-
t.Fatalf("unexpected error: %v", err)
|
|
258
|
-
}
|
|
259
|
-
if !offer.Price.Equal(newPrice) {
|
|
260
|
-
t.Errorf("price not updated")
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
Run test - it should FAIL.
|
|
266
|
-
|
|
267
|
-
### 2. GREEN - Minimal Implementation
|
|
80
|
+
Mock when:
|
|
81
|
+
- External services (HTTP, DB)
|
|
82
|
+
- Non-deterministic (time, random)
|
|
83
|
+
- Slow operations
|
|
268
84
|
|
|
269
|
-
|
|
270
|
-
func (o *Offer) UpdatePrice(price decimal.Decimal) error {
|
|
271
|
-
o.Price = price
|
|
272
|
-
return nil
|
|
273
|
-
}
|
|
85
|
+
Prefer interfaces for testability:
|
|
274
86
|
```
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
### 3. REFACTOR - Improve Code
|
|
279
|
-
|
|
280
|
-
```go
|
|
281
|
-
func (o *Offer) UpdatePrice(price decimal.Decimal) error {
|
|
282
|
-
if price.LessThanOrEqual(decimal.Zero) {
|
|
283
|
-
return ErrInvalidPrice
|
|
284
|
-
}
|
|
285
|
-
o.Price = price
|
|
286
|
-
return nil
|
|
87
|
+
type Repository interface {
|
|
88
|
+
Find(id string) (*Entity, error)
|
|
287
89
|
}
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
Run test - it should still PASS.
|
|
291
|
-
|
|
292
|
-
Add test for new validation:
|
|
293
90
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
offer := NewTestOffer()
|
|
297
|
-
|
|
298
|
-
err := offer.UpdatePrice(decimal.Zero)
|
|
299
|
-
|
|
300
|
-
if !errors.Is(err, ErrInvalidPrice) {
|
|
301
|
-
t.Errorf("expected ErrInvalidPrice, got %v", err)
|
|
302
|
-
}
|
|
303
|
-
}
|
|
91
|
+
// Production: RealRepository
|
|
92
|
+
// Tests: MockRepository
|
|
304
93
|
```
|
|
305
94
|
|
|
306
|
-
## Test
|
|
307
|
-
|
|
308
|
-
```bash
|
|
309
|
-
# Run all tests
|
|
310
|
-
go test ./...
|
|
95
|
+
## Test Isolation
|
|
311
96
|
|
|
312
|
-
|
|
313
|
-
|
|
97
|
+
Each test should:
|
|
98
|
+
- Setup its own data
|
|
99
|
+
- Not depend on other tests
|
|
100
|
+
- Clean up after itself
|
|
101
|
+
- Run in any order
|
|
314
102
|
|
|
315
|
-
|
|
316
|
-
go test -run TestOffer_UpdatePrice ./...
|
|
103
|
+
## Coverage Guidelines
|
|
317
104
|
|
|
318
|
-
|
|
319
|
-
|
|
105
|
+
| Layer | Target |
|
|
106
|
+
|-------|--------|
|
|
107
|
+
| Domain/Business | 80%+ |
|
|
108
|
+
| Application/UseCase | 70%+ |
|
|
109
|
+
| Infrastructure | 50%+ |
|
|
320
110
|
|
|
321
|
-
|
|
322
|
-
go test -v ./...
|
|
323
|
-
```
|
|
111
|
+
Focus on critical paths, not 100% coverage.
|