specweave 1.0.239 → 1.0.241
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/CLAUDE.md +31 -30
- package/README.md +1 -1
- package/bin/specweave.js +16 -0
- package/dist/plugins/specweave-ado/lib/ado-permission-gate.d.ts.map +1 -1
- package/dist/plugins/specweave-ado/lib/ado-permission-gate.js +17 -2
- package/dist/plugins/specweave-ado/lib/ado-permission-gate.js.map +1 -1
- package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts +7 -0
- package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts.map +1 -1
- package/dist/plugins/specweave-github/lib/github-feature-sync.js +53 -0
- package/dist/plugins/specweave-github/lib/github-feature-sync.js.map +1 -1
- package/dist/plugins/specweave-jira/lib/jira-permission-gate.d.ts.map +1 -1
- package/dist/plugins/specweave-jira/lib/jira-permission-gate.js +17 -2
- package/dist/plugins/specweave-jira/lib/jira-permission-gate.js.map +1 -1
- package/dist/plugins/specweave-testing/lib/playwright-cli-detector.d.ts +1 -0
- package/dist/plugins/specweave-testing/lib/playwright-cli-detector.d.ts.map +1 -1
- package/dist/plugins/specweave-testing/lib/playwright-cli-detector.js +7 -3
- package/dist/plugins/specweave-testing/lib/playwright-cli-detector.js.map +1 -1
- package/dist/plugins/specweave-testing/lib/playwright-cli-runner.d.ts.map +1 -1
- package/dist/plugins/specweave-testing/lib/playwright-cli-runner.js +27 -19
- package/dist/plugins/specweave-testing/lib/playwright-cli-runner.js.map +1 -1
- package/dist/plugins/specweave-testing/lib/playwright-routing.d.ts +8 -0
- package/dist/plugins/specweave-testing/lib/playwright-routing.d.ts.map +1 -1
- package/dist/plugins/specweave-testing/lib/playwright-routing.js +10 -7
- package/dist/plugins/specweave-testing/lib/playwright-routing.js.map +1 -1
- package/dist/src/adapters/agents-md-generator.js +1 -1
- package/dist/src/adapters/agents-md-generator.js.map +1 -1
- package/dist/src/adapters/claude/README.md +1 -1
- package/dist/src/adapters/claude-md-generator.js +1 -1
- package/dist/src/adapters/claude-md-generator.js.map +1 -1
- package/dist/src/cli/commands/init.d.ts.map +1 -1
- package/dist/src/cli/commands/init.js +10 -1
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/commands/refresh-marketplace.d.ts.map +1 -1
- package/dist/src/cli/commands/refresh-marketplace.js +7 -67
- package/dist/src/cli/commands/refresh-marketplace.js.map +1 -1
- package/dist/src/cli/commands/team.d.ts +20 -0
- package/dist/src/cli/commands/team.d.ts.map +1 -0
- package/dist/src/cli/commands/team.js +101 -0
- package/dist/src/cli/commands/team.js.map +1 -0
- package/dist/src/cli/helpers/init/claude-settings-env.d.ts +16 -0
- package/dist/src/cli/helpers/init/claude-settings-env.d.ts.map +1 -0
- package/dist/src/cli/helpers/init/claude-settings-env.js +44 -0
- package/dist/src/cli/helpers/init/claude-settings-env.js.map +1 -0
- package/dist/src/cli/helpers/init/plugin-installer.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/plugin-installer.js +9 -13
- package/dist/src/cli/helpers/init/plugin-installer.js.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/index.d.ts.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/index.js +12 -6
- package/dist/src/cli/helpers/issue-tracker/index.js.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/types.d.ts +2 -0
- package/dist/src/cli/helpers/issue-tracker/types.d.ts.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/types.js.map +1 -1
- package/dist/src/core/increment/discipline-checker.js +1 -1
- package/dist/src/core/increment/discipline-checker.js.map +1 -1
- package/dist/src/core/increment/status-commands.d.ts.map +1 -1
- package/dist/src/core/increment/status-commands.js +7 -0
- package/dist/src/core/increment/status-commands.js.map +1 -1
- package/dist/src/core/lazy-loading/llm-plugin-detector.d.ts +2 -2
- package/dist/src/core/lazy-loading/llm-plugin-detector.d.ts.map +1 -1
- package/dist/src/core/lazy-loading/llm-plugin-detector.js +63 -25
- package/dist/src/core/lazy-loading/llm-plugin-detector.js.map +1 -1
- package/dist/src/core/reflection/reflect-handler.js +2 -2
- package/dist/src/core/reflection/reflect-handler.js.map +1 -1
- package/dist/src/core/session/handoff-context.js +2 -2
- package/dist/src/core/session/handoff-context.js.map +1 -1
- package/dist/src/sync/ado-reconciler.d.ts.map +1 -1
- package/dist/src/sync/ado-reconciler.js +21 -2
- package/dist/src/sync/ado-reconciler.js.map +1 -1
- package/dist/src/sync/engine.d.ts.map +1 -1
- package/dist/src/sync/engine.js +2 -0
- package/dist/src/sync/engine.js.map +1 -1
- package/dist/src/sync/github-reconciler.d.ts.map +1 -1
- package/dist/src/sync/github-reconciler.js +52 -26
- package/dist/src/sync/github-reconciler.js.map +1 -1
- package/dist/src/sync/jira-reconciler.d.ts.map +1 -1
- package/dist/src/sync/jira-reconciler.js +16 -3
- package/dist/src/sync/jira-reconciler.js.map +1 -1
- package/dist/src/sync/providers/ado.d.ts.map +1 -1
- package/dist/src/sync/providers/ado.js +4 -2
- package/dist/src/sync/providers/ado.js.map +1 -1
- package/dist/src/sync/providers/github.d.ts.map +1 -1
- package/dist/src/sync/providers/github.js +11 -0
- package/dist/src/sync/providers/github.js.map +1 -1
- package/dist/src/sync/providers/jira.d.ts.map +1 -1
- package/dist/src/sync/providers/jira.js +14 -2
- package/dist/src/sync/providers/jira.js.map +1 -1
- package/dist/src/sync/sync-coordinator.d.ts.map +1 -1
- package/dist/src/sync/sync-coordinator.js +31 -6
- package/dist/src/sync/sync-coordinator.js.map +1 -1
- package/dist/src/utils/auto-install.js +4 -4
- package/dist/src/utils/auto-install.js.map +1 -1
- package/package.json +2 -2
- package/plugins/FINAL-AUDIT-RECOMMENDATIONS.md +3 -3
- package/plugins/SKILLS-VS-AGENTS.md +1 -1
- package/plugins/specweave/PLUGIN.md +0 -2
- package/plugins/specweave/commands/export-skills.md +1 -1
- package/plugins/specweave/commands/role-orchestrator.md +1 -1
- package/plugins/specweave/hooks/log-decision.sh +6 -0
- package/plugins/specweave/hooks/stop-auto-v5.sh +17 -1
- package/plugins/specweave/hooks/stop-reflect.sh +16 -2
- package/plugins/specweave/hooks/stop-sync.sh +17 -9
- package/plugins/specweave/hooks/user-prompt-submit.sh +119 -35
- package/plugins/specweave/lib/vendor/sync/github-reconciler.js +52 -26
- package/plugins/specweave/lib/vendor/sync/github-reconciler.js.map +1 -1
- package/plugins/specweave/scripts/read-grill-context.sh +149 -0
- package/plugins/specweave/skills/code-review/SKILL.md +608 -0
- package/plugins/specweave/skills/done/SKILL.md +1 -1
- package/plugins/specweave/skills/grill/SKILL.md +91 -0
- package/plugins/specweave/skills/performance/SKILL.md +6 -0
- package/plugins/specweave/skills/security/SKILL.md +7 -0
- package/plugins/specweave/skills/security-patterns/SKILL.md +6 -0
- package/plugins/specweave/skills/tdd-orchestrator/SKILL.md +1 -1
- package/plugins/specweave/skills/team-build/SKILL.md +1 -1
- package/plugins/specweave/skills/team-orchestrate/SKILL.md +1 -1
- package/plugins/specweave/skills/tech-lead/SKILL.md +7 -0
- package/plugins/specweave-ado/lib/ado-permission-gate.js +18 -2
- package/plugins/specweave-ado/lib/ado-permission-gate.ts +19 -2
- package/plugins/specweave-frontend/skills/frontend/SKILL.md +138 -2
- package/plugins/specweave-frontend/skills/i18n-expert/SKILL.md +989 -0
- package/plugins/specweave-github/hooks/github-auto-create-handler.sh +23 -1
- package/plugins/specweave-github/lib/github-feature-sync.js +41 -0
- package/plugins/specweave-github/lib/github-feature-sync.ts +62 -0
- package/plugins/specweave-infrastructure/PLUGIN.md +2 -1
- package/plugins/specweave-infrastructure/skills/gcp-deep-dive/SKILL.md +1172 -0
- package/plugins/specweave-infrastructure/skills/observability/SKILL.md +6 -0
- package/plugins/specweave-infrastructure/skills/opentelemetry/SKILL.md +6 -0
- package/plugins/specweave-jira/lib/jira-permission-gate.js +18 -2
- package/plugins/specweave-jira/lib/jira-permission-gate.ts +19 -2
- package/plugins/specweave-mobile/PLUGIN.md +1 -2
- package/plugins/specweave-mobile/README.md +13 -12
- package/plugins/specweave-mobile/skills/capacitor-ionic/SKILL.md +4 -18
- package/plugins/specweave-mobile/skills/deep-linking-push/SKILL.md +4 -22
- package/plugins/specweave-mobile/skills/expo/SKILL.md +4 -24
- package/plugins/specweave-mobile/skills/mobile-testing/SKILL.md +4 -22
- package/plugins/specweave-mobile/skills/react-native-expert/SKILL.md +404 -47
- package/plugins/specweave-testing/PLUGIN.md +3 -11
- package/plugins/specweave-testing/lib/playwright-cli-detector.js +8 -3
- package/plugins/specweave-testing/lib/playwright-cli-detector.ts +8 -3
- package/plugins/specweave-testing/lib/playwright-cli-runner.js +25 -20
- package/plugins/specweave-testing/lib/playwright-cli-runner.ts +24 -19
- package/plugins/specweave-testing/lib/playwright-routing.js +1 -6
- package/plugins/specweave-testing/lib/playwright-routing.ts +11 -8
- package/plugins/specweave-testing/skills/accessibility-testing/SKILL.md +998 -0
- package/plugins/specweave-testing/skills/e2e-testing/SKILL.md +29 -28
- package/plugins/specweave-testing/skills/mutation-testing/SKILL.md +769 -0
- package/plugins/specweave-testing/skills/performance-testing/SKILL.md +961 -0
- package/plugins/specweave-testing/skills/qa-engineer/SKILL.md +2 -0
- package/plugins/specweave/.specweave/logs/decisions.jsonl +0 -12
- package/plugins/specweave/.specweave/logs/reflect/reflect.log +0 -8
- package/plugins/specweave/.specweave/logs/stop-auto.log +0 -6
- package/plugins/specweave/.specweave/logs/stop-sync.log +0 -10
- package/plugins/specweave/.specweave/state/dashboard.json +0 -43
- package/plugins/specweave/skills/infrastructure/SKILL.md +0 -86
- package/plugins/specweave/skills/qa-lead/SKILL.md +0 -77
- package/plugins/specweave-mobile/skills/mobile-architect/SKILL.md +0 -30
- package/plugins/specweave-testing/commands/e2e-setup.md +0 -1103
- package/plugins/specweave-testing/commands/test-coverage.md +0 -983
- package/plugins/specweave-testing/commands/test-generate.md +0 -1160
- package/plugins/specweave-testing/commands/test-init.md +0 -413
- package/plugins/specweave-testing/commands/ui-automate.md +0 -182
- package/plugins/specweave-testing/commands/ui-inspect.md +0 -82
|
@@ -0,0 +1,769 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Mutation testing expert using Stryker Mutator for TypeScript/JavaScript. Analyzes test suite effectiveness by injecting code mutations and measuring kill rates. Use for mutation testing setup, score analysis, survived mutant triage, CI integration, and improving test quality beyond code coverage.
|
|
3
|
+
allowed-tools: Read, Write, Edit, Bash
|
|
4
|
+
model: opus
|
|
5
|
+
context: fork
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Mutation Testing Expert - Stryker Mutator
|
|
9
|
+
|
|
10
|
+
## When to Use
|
|
11
|
+
|
|
12
|
+
Trigger this skill when the user mentions: "mutation testing", "Stryker", "test quality",
|
|
13
|
+
"mutation score", "survived mutants", "mutant", "test effectiveness", "kill rate",
|
|
14
|
+
"are my tests good enough", "test suite quality", "weak tests".
|
|
15
|
+
|
|
16
|
+
## What Is Mutation Testing
|
|
17
|
+
|
|
18
|
+
Mutation testing measures the **effectiveness** of your test suite, not just what code it
|
|
19
|
+
executes. Code coverage tells you which lines ran; mutation testing tells you whether your
|
|
20
|
+
tests actually **detect bugs**.
|
|
21
|
+
|
|
22
|
+
The process:
|
|
23
|
+
1. A tool (Stryker) injects small faults ("mutants") into your source code
|
|
24
|
+
2. Your test suite runs against each mutant
|
|
25
|
+
3. If a test fails, the mutant is **killed** (good -- your tests caught it)
|
|
26
|
+
4. If all tests pass, the mutant **survived** (bad -- your tests missed a real bug)
|
|
27
|
+
|
|
28
|
+
**Mutation score** = killed mutants / total mutants x 100
|
|
29
|
+
|
|
30
|
+
### Why It Matters
|
|
31
|
+
|
|
32
|
+
- **80% code coverage can miss 50% of bugs** -- coverage is necessary but not sufficient
|
|
33
|
+
- Mutation testing reveals **weak assertions**, missing edge cases, and untested branches
|
|
34
|
+
- It answers: "If a bug were introduced here, would my tests catch it?"
|
|
35
|
+
|
|
36
|
+
## Stryker Mutator (Primary Tool)
|
|
37
|
+
|
|
38
|
+
### Installation
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Install Stryker for a TypeScript/JavaScript project
|
|
42
|
+
npm install --save-dev @stryker-mutator/core @stryker-mutator/typescript-checker @stryker-mutator/vitest-runner
|
|
43
|
+
|
|
44
|
+
# For Jest projects
|
|
45
|
+
npm install --save-dev @stryker-mutator/core @stryker-mutator/typescript-checker @stryker-mutator/jest-runner
|
|
46
|
+
|
|
47
|
+
# Initialize configuration (interactive)
|
|
48
|
+
npx stryker init
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Configuration: stryker.config.mjs
|
|
52
|
+
|
|
53
|
+
**Minimal configuration for a Vitest project:**
|
|
54
|
+
|
|
55
|
+
```javascript
|
|
56
|
+
// stryker.config.mjs
|
|
57
|
+
/** @type {import('@stryker-mutator/api/core').PartialStrykerOptions} */
|
|
58
|
+
export default {
|
|
59
|
+
mutate: [
|
|
60
|
+
'src/**/*.ts',
|
|
61
|
+
'!src/**/*.test.ts',
|
|
62
|
+
'!src/**/*.spec.ts',
|
|
63
|
+
'!src/**/*.d.ts',
|
|
64
|
+
'!src/**/index.ts',
|
|
65
|
+
],
|
|
66
|
+
testRunner: 'vitest',
|
|
67
|
+
checkers: ['typescript'],
|
|
68
|
+
tsconfigFile: 'tsconfig.json',
|
|
69
|
+
reporters: ['html', 'clear-text', 'progress', 'dashboard'],
|
|
70
|
+
coverageAnalysis: 'perTest',
|
|
71
|
+
timeoutMS: 60000,
|
|
72
|
+
thresholds: {
|
|
73
|
+
high: 80,
|
|
74
|
+
low: 60,
|
|
75
|
+
break: null, // Set to a number to fail CI below this score
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Jest configuration:**
|
|
81
|
+
|
|
82
|
+
```javascript
|
|
83
|
+
// stryker.config.mjs
|
|
84
|
+
/** @type {import('@stryker-mutator/api/core').PartialStrykerOptions} */
|
|
85
|
+
export default {
|
|
86
|
+
mutate: [
|
|
87
|
+
'src/**/*.ts',
|
|
88
|
+
'!src/**/*.test.ts',
|
|
89
|
+
'!src/**/*.spec.ts',
|
|
90
|
+
],
|
|
91
|
+
testRunner: 'jest',
|
|
92
|
+
jest: {
|
|
93
|
+
configFile: 'jest.config.ts',
|
|
94
|
+
},
|
|
95
|
+
checkers: ['typescript'],
|
|
96
|
+
reporters: ['html', 'clear-text', 'progress'],
|
|
97
|
+
coverageAnalysis: 'perTest',
|
|
98
|
+
};
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Targeted configuration for critical modules only:**
|
|
102
|
+
|
|
103
|
+
```javascript
|
|
104
|
+
// stryker.config.mjs
|
|
105
|
+
export default {
|
|
106
|
+
mutate: [
|
|
107
|
+
// Only mutate business-critical code
|
|
108
|
+
'src/services/payment/**/*.ts',
|
|
109
|
+
'src/services/auth/**/*.ts',
|
|
110
|
+
'src/utils/validators/**/*.ts',
|
|
111
|
+
'src/core/parser/**/*.ts',
|
|
112
|
+
'!src/**/*.test.ts',
|
|
113
|
+
'!src/**/*.d.ts',
|
|
114
|
+
],
|
|
115
|
+
testRunner: 'vitest',
|
|
116
|
+
checkers: ['typescript'],
|
|
117
|
+
reporters: ['html', 'clear-text', 'progress'],
|
|
118
|
+
coverageAnalysis: 'perTest',
|
|
119
|
+
timeoutMS: 120000,
|
|
120
|
+
concurrency: 4, // Limit parallel workers
|
|
121
|
+
};
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Running Stryker
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
# Full mutation testing run
|
|
128
|
+
npx stryker run
|
|
129
|
+
|
|
130
|
+
# Target specific files
|
|
131
|
+
npx stryker run --mutate 'src/services/auth/**/*.ts'
|
|
132
|
+
|
|
133
|
+
# Incremental mode (only re-test changed code)
|
|
134
|
+
npx stryker run --incremental
|
|
135
|
+
|
|
136
|
+
# With specific log level for debugging
|
|
137
|
+
npx stryker run --logLevel debug
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Incremental Mode
|
|
141
|
+
|
|
142
|
+
Incremental mode is essential for fast feedback. It caches previous results and only
|
|
143
|
+
re-mutates files that changed since the last run.
|
|
144
|
+
|
|
145
|
+
```javascript
|
|
146
|
+
// stryker.config.mjs
|
|
147
|
+
export default {
|
|
148
|
+
// ... other config
|
|
149
|
+
incremental: true,
|
|
150
|
+
incrementalFile: '.stryker-cache/incremental.json',
|
|
151
|
+
};
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
# First run: full analysis (slow)
|
|
156
|
+
npx stryker run
|
|
157
|
+
|
|
158
|
+
# Subsequent runs: only changed files (fast)
|
|
159
|
+
npx stryker run --incremental
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Add `.stryker-cache/` and `.stryker-tmp/` to `.gitignore`.
|
|
163
|
+
|
|
164
|
+
## Mutation Operators
|
|
165
|
+
|
|
166
|
+
Stryker applies these mutation operators to your source code:
|
|
167
|
+
|
|
168
|
+
### Arithmetic Mutations
|
|
169
|
+
| Original | Mutant |
|
|
170
|
+
|----------|--------|
|
|
171
|
+
| `a + b` | `a - b` |
|
|
172
|
+
| `a - b` | `a + b` |
|
|
173
|
+
| `a * b` | `a / b` |
|
|
174
|
+
| `a / b` | `a * b` |
|
|
175
|
+
| `a % b` | `a * b` |
|
|
176
|
+
|
|
177
|
+
### Conditional Mutations
|
|
178
|
+
| Original | Mutant |
|
|
179
|
+
|----------|--------|
|
|
180
|
+
| `a > b` | `a >= b`, `a < b` |
|
|
181
|
+
| `a < b` | `a <= b`, `a > b` |
|
|
182
|
+
| `a >= b` | `a > b`, `a <= b` |
|
|
183
|
+
| `a <= b` | `a < b`, `a >= b` |
|
|
184
|
+
| `a === b` | `a !== b` |
|
|
185
|
+
| `a !== b` | `a === b` |
|
|
186
|
+
|
|
187
|
+
### Logical Mutations
|
|
188
|
+
| Original | Mutant |
|
|
189
|
+
|----------|--------|
|
|
190
|
+
| `a && b` | `a \|\| b` |
|
|
191
|
+
| `a \|\| b` | `a && b` |
|
|
192
|
+
| `!a` | `a` |
|
|
193
|
+
|
|
194
|
+
### String Mutations
|
|
195
|
+
| Original | Mutant |
|
|
196
|
+
|----------|--------|
|
|
197
|
+
| `"foo"` | `""` |
|
|
198
|
+
| `""` | `"Stryker was here!"` |
|
|
199
|
+
| `` `template ${x}` `` | `` `template` `` |
|
|
200
|
+
|
|
201
|
+
### Array Mutations
|
|
202
|
+
| Original | Mutant |
|
|
203
|
+
|----------|--------|
|
|
204
|
+
| `[].push(x)` | removed |
|
|
205
|
+
| `[].pop()` | removed |
|
|
206
|
+
| `[].sort()` | removed |
|
|
207
|
+
| `[].filter(fn)` | `[]` |
|
|
208
|
+
| `[].map(fn)` | `[]` |
|
|
209
|
+
|
|
210
|
+
### Unary Mutations
|
|
211
|
+
| Original | Mutant |
|
|
212
|
+
|----------|--------|
|
|
213
|
+
| `+a` | `-a` |
|
|
214
|
+
| `-a` | `+a` |
|
|
215
|
+
| `~a` | `a` |
|
|
216
|
+
|
|
217
|
+
### Block Statement Mutations
|
|
218
|
+
| Original | Mutant |
|
|
219
|
+
|----------|--------|
|
|
220
|
+
| `if (cond) { block }` | `if (cond) { }` |
|
|
221
|
+
| `function() { body }` | `function() { }` |
|
|
222
|
+
|
|
223
|
+
### Boolean Mutations
|
|
224
|
+
| Original | Mutant |
|
|
225
|
+
|----------|--------|
|
|
226
|
+
| `true` | `false` |
|
|
227
|
+
| `false` | `true` |
|
|
228
|
+
|
|
229
|
+
### Optional Chaining
|
|
230
|
+
| Original | Mutant |
|
|
231
|
+
|----------|--------|
|
|
232
|
+
| `a?.b` | `a.b` |
|
|
233
|
+
| `a?.[b]` | `a[b]` |
|
|
234
|
+
| `a?.()` | `a()` |
|
|
235
|
+
|
|
236
|
+
## Mutation Score Analysis
|
|
237
|
+
|
|
238
|
+
### Understanding the Report
|
|
239
|
+
|
|
240
|
+
After running Stryker, examine the HTML report at `reports/mutation/html/index.html`.
|
|
241
|
+
|
|
242
|
+
**Score interpretation:**
|
|
243
|
+
- **80%+**: Strong test suite -- tests catch most injected faults
|
|
244
|
+
- **60-79%**: Adequate but has gaps -- review survived mutants
|
|
245
|
+
- **Below 60%**: Significant weaknesses -- tests give false confidence
|
|
246
|
+
|
|
247
|
+
**Mutant statuses:**
|
|
248
|
+
- **Killed**: A test failed when this mutant was active (good)
|
|
249
|
+
- **Survived**: All tests passed despite the mutation (bad -- add/fix tests)
|
|
250
|
+
- **No coverage**: No test executes this code at all (worst -- add tests)
|
|
251
|
+
- **Timeout**: Mutant caused an infinite loop (usually counts as killed)
|
|
252
|
+
- **Compile error**: Mutant produced invalid code (ignored)
|
|
253
|
+
|
|
254
|
+
### Triaging Survived Mutants
|
|
255
|
+
|
|
256
|
+
When a mutant survives, ask these questions in order:
|
|
257
|
+
|
|
258
|
+
**1. Is it an equivalent mutant?**
|
|
259
|
+
An equivalent mutant changes the code but produces identical behavior. It cannot be
|
|
260
|
+
killed because no observable difference exists.
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
// Original
|
|
264
|
+
function getIndex(arr: string[], item: string): number {
|
|
265
|
+
return arr.indexOf(item);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Equivalent mutant: changing >= 0 to > 0 in a caller
|
|
269
|
+
// If indexOf never returns 0 in practice, these are equivalent
|
|
270
|
+
if (getIndex(items, 'x') >= 0) { ... }
|
|
271
|
+
if (getIndex(items, 'x') > 0) { ... } // equivalent if 'x' is never first
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Mark equivalent mutants in Stryker config to exclude them from the score:
|
|
275
|
+
|
|
276
|
+
```javascript
|
|
277
|
+
// stryker.config.mjs
|
|
278
|
+
export default {
|
|
279
|
+
mutate: [
|
|
280
|
+
'src/**/*.ts',
|
|
281
|
+
// Exclude known equivalent mutant locations
|
|
282
|
+
'!src/utils/constants.ts',
|
|
283
|
+
],
|
|
284
|
+
};
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
**2. Is the assertion weak?**
|
|
288
|
+
The most common cause of survived mutants. The test runs the code but does not assert
|
|
289
|
+
on the affected value.
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
// Survived mutant: `price * quantity` -> `price / quantity`
|
|
293
|
+
function calculateTotal(price: number, quantity: number): number {
|
|
294
|
+
return price * quantity;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// WEAK test -- does not catch arithmetic mutation
|
|
298
|
+
it('should calculate total', () => {
|
|
299
|
+
const result = calculateTotal(10, 1); // 10 * 1 === 10 / 1
|
|
300
|
+
expect(result).toBe(10);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// STRONG test -- catches the mutation
|
|
304
|
+
it('should calculate total', () => {
|
|
305
|
+
const result = calculateTotal(10, 3); // 10 * 3 = 30, 10 / 3 = 3.33
|
|
306
|
+
expect(result).toBe(30);
|
|
307
|
+
});
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
**3. Is the boundary untested?**
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
// Survived mutant: `age >= 18` -> `age > 18`
|
|
314
|
+
function canVote(age: number): boolean {
|
|
315
|
+
return age >= 18;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Missing boundary test
|
|
319
|
+
it('should allow voting at 18', () => {
|
|
320
|
+
expect(canVote(18)).toBe(true); // Kills >= -> > mutant
|
|
321
|
+
expect(canVote(17)).toBe(false); // Kills >= -> <= mutant
|
|
322
|
+
});
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
**4. Is the code actually untestable or dead?**
|
|
326
|
+
If no test can reach the mutated code, you have dead code. Remove it.
|
|
327
|
+
|
|
328
|
+
### Score Improvement Strategies
|
|
329
|
+
|
|
330
|
+
**Priority 1: Fix "No coverage" mutants**
|
|
331
|
+
These indicate completely untested code. Write tests that at minimum execute these paths.
|
|
332
|
+
|
|
333
|
+
**Priority 2: Strengthen weak assertions**
|
|
334
|
+
Look for tests that execute the code but use loose assertions:
|
|
335
|
+
```typescript
|
|
336
|
+
// Weak: only checks truthiness
|
|
337
|
+
expect(result).toBeTruthy();
|
|
338
|
+
|
|
339
|
+
// Strong: checks exact value
|
|
340
|
+
expect(result).toEqual({ total: 30, tax: 2.70 });
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
**Priority 3: Add boundary tests**
|
|
344
|
+
For every comparison operator, test the exact boundary value:
|
|
345
|
+
```typescript
|
|
346
|
+
describe('isAdult', () => {
|
|
347
|
+
it('should return true at exactly 18', () => expect(isAdult(18)).toBe(true));
|
|
348
|
+
it('should return false at 17', () => expect(isAdult(17)).toBe(false));
|
|
349
|
+
it('should return true above 18', () => expect(isAdult(25)).toBe(true));
|
|
350
|
+
});
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
**Priority 4: Test negation and logical paths**
|
|
354
|
+
Ensure both branches of every `if/else` and `&&/||` are tested.
|
|
355
|
+
|
|
356
|
+
## Integration Patterns
|
|
357
|
+
|
|
358
|
+
### GitHub Actions Workflow
|
|
359
|
+
|
|
360
|
+
```yaml
|
|
361
|
+
# .github/workflows/mutation-testing.yml
|
|
362
|
+
name: Mutation Testing
|
|
363
|
+
|
|
364
|
+
on:
|
|
365
|
+
pull_request:
|
|
366
|
+
branches: [main, develop]
|
|
367
|
+
# Optional: scheduled full run
|
|
368
|
+
schedule:
|
|
369
|
+
- cron: '0 3 * * 1' # Weekly Monday 3am
|
|
370
|
+
|
|
371
|
+
jobs:
|
|
372
|
+
mutation-test:
|
|
373
|
+
runs-on: ubuntu-latest
|
|
374
|
+
timeout-minutes: 60
|
|
375
|
+
|
|
376
|
+
steps:
|
|
377
|
+
- uses: actions/checkout@v4
|
|
378
|
+
with:
|
|
379
|
+
fetch-depth: 0 # Needed for incremental mode
|
|
380
|
+
|
|
381
|
+
- uses: actions/setup-node@v4
|
|
382
|
+
with:
|
|
383
|
+
node-version: 20
|
|
384
|
+
cache: 'npm'
|
|
385
|
+
|
|
386
|
+
- run: npm ci
|
|
387
|
+
|
|
388
|
+
- name: Restore Stryker cache
|
|
389
|
+
uses: actions/cache@v4
|
|
390
|
+
with:
|
|
391
|
+
path: .stryker-cache
|
|
392
|
+
key: stryker-${{ runner.os }}-${{ hashFiles('src/**/*.ts') }}
|
|
393
|
+
restore-keys: |
|
|
394
|
+
stryker-${{ runner.os }}-
|
|
395
|
+
|
|
396
|
+
- name: Run mutation testing (incremental)
|
|
397
|
+
run: npx stryker run --incremental
|
|
398
|
+
|
|
399
|
+
- name: Upload mutation report
|
|
400
|
+
if: always()
|
|
401
|
+
uses: actions/upload-artifact@v4
|
|
402
|
+
with:
|
|
403
|
+
name: mutation-report
|
|
404
|
+
path: reports/mutation/
|
|
405
|
+
retention-days: 14
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### Incremental PR-Only Testing
|
|
409
|
+
|
|
410
|
+
For faster CI, only mutate files changed in the PR:
|
|
411
|
+
|
|
412
|
+
```yaml
|
|
413
|
+
- name: Get changed files
|
|
414
|
+
id: changed
|
|
415
|
+
run: |
|
|
416
|
+
FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD \
|
|
417
|
+
-- 'src/**/*.ts' \
|
|
418
|
+
| grep -v '\.test\.' \
|
|
419
|
+
| grep -v '\.spec\.' \
|
|
420
|
+
| grep -v '\.d\.ts' \
|
|
421
|
+
| tr '\n' ',' | sed 's/,$//')
|
|
422
|
+
echo "files=$FILES" >> $GITHUB_OUTPUT
|
|
423
|
+
|
|
424
|
+
- name: Run mutation testing on changed files
|
|
425
|
+
if: steps.changed.outputs.files != ''
|
|
426
|
+
run: |
|
|
427
|
+
npx stryker run --mutate "${{ steps.changed.outputs.files }}"
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
### Report Formats
|
|
431
|
+
|
|
432
|
+
Configure multiple reporters in `stryker.config.mjs`:
|
|
433
|
+
|
|
434
|
+
```javascript
|
|
435
|
+
export default {
|
|
436
|
+
reporters: [
|
|
437
|
+
'html', // Detailed HTML report in reports/mutation/html/
|
|
438
|
+
'clear-text', // Terminal summary
|
|
439
|
+
'progress', // Progress bar during execution
|
|
440
|
+
'json', // Machine-readable JSON in reports/mutation/mutation.json
|
|
441
|
+
'dashboard', // Stryker dashboard (stryker-mutator.io/dashboard)
|
|
442
|
+
],
|
|
443
|
+
htmlReporter: {
|
|
444
|
+
fileName: 'reports/mutation/html/index.html',
|
|
445
|
+
},
|
|
446
|
+
jsonReporter: {
|
|
447
|
+
fileName: 'reports/mutation/mutation.json',
|
|
448
|
+
},
|
|
449
|
+
// Dashboard reporter config (requires STRYKER_DASHBOARD_API_KEY)
|
|
450
|
+
dashboard: {
|
|
451
|
+
project: 'github.com/your-org/your-repo',
|
|
452
|
+
version: 'main',
|
|
453
|
+
module: 'core',
|
|
454
|
+
},
|
|
455
|
+
};
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
### Setting a CI Quality Gate
|
|
459
|
+
|
|
460
|
+
Fail the build if mutation score drops below a threshold:
|
|
461
|
+
|
|
462
|
+
```javascript
|
|
463
|
+
// stryker.config.mjs
|
|
464
|
+
export default {
|
|
465
|
+
thresholds: {
|
|
466
|
+
high: 80, // Green in report
|
|
467
|
+
low: 60, // Yellow in report
|
|
468
|
+
break: 60, // FAIL CI if score drops below this
|
|
469
|
+
},
|
|
470
|
+
};
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
### Integration with Existing Test Scripts
|
|
474
|
+
|
|
475
|
+
```json
|
|
476
|
+
{
|
|
477
|
+
"scripts": {
|
|
478
|
+
"test": "vitest run",
|
|
479
|
+
"test:mutation": "stryker run",
|
|
480
|
+
"test:mutation:incremental": "stryker run --incremental",
|
|
481
|
+
"test:mutation:changed": "stryker run --incremental --since main",
|
|
482
|
+
"test:all": "npm run test && npm run test:mutation:incremental"
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
## Strategy and Best Practices
|
|
488
|
+
|
|
489
|
+
### When Mutation Testing Is Valuable
|
|
490
|
+
|
|
491
|
+
**High value -- always mutate:**
|
|
492
|
+
- Payment/billing logic
|
|
493
|
+
- Authentication and authorization
|
|
494
|
+
- Data validation and sanitization
|
|
495
|
+
- Parsers and serializers
|
|
496
|
+
- State machines and workflow engines
|
|
497
|
+
- Cryptographic operations
|
|
498
|
+
- Rate limiters and throttlers
|
|
499
|
+
- Business rule engines
|
|
500
|
+
|
|
501
|
+
**Medium value -- mutate selectively:**
|
|
502
|
+
- API route handlers (focus on error handling)
|
|
503
|
+
- Database query builders (focus on WHERE clauses)
|
|
504
|
+
- Middleware chains (focus on short-circuit logic)
|
|
505
|
+
|
|
506
|
+
**Low value -- skip or defer:**
|
|
507
|
+
- UI components (visual testing is more effective)
|
|
508
|
+
- Configuration files and constants
|
|
509
|
+
- Auto-generated code (OpenAPI clients, Prisma, GraphQL codegen)
|
|
510
|
+
- Thin wrappers around third-party libraries
|
|
511
|
+
- Logging and telemetry code
|
|
512
|
+
- Migration scripts
|
|
513
|
+
|
|
514
|
+
### Balancing Quality vs Execution Time
|
|
515
|
+
|
|
516
|
+
Mutation testing is CPU-intensive. A full run on a large codebase can take 30+ minutes.
|
|
517
|
+
|
|
518
|
+
**Strategies for speed:**
|
|
519
|
+
|
|
520
|
+
1. **Incremental mode** (always use in development):
|
|
521
|
+
```bash
|
|
522
|
+
npx stryker run --incremental
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
2. **Target critical modules only** -- do not mutate everything:
|
|
526
|
+
```javascript
|
|
527
|
+
mutate: [
|
|
528
|
+
'src/core/**/*.ts',
|
|
529
|
+
'src/services/payment/**/*.ts',
|
|
530
|
+
'!src/**/*.test.ts',
|
|
531
|
+
],
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
3. **Limit concurrency** to avoid OOM on CI:
|
|
535
|
+
```javascript
|
|
536
|
+
concurrency: Math.max(1, require('os').cpus().length - 1),
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
4. **Use `perTest` coverage analysis** -- Stryker only runs tests that cover each mutant:
|
|
540
|
+
```javascript
|
|
541
|
+
coverageAnalysis: 'perTest',
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
5. **Increase timeout for slow test suites**:
|
|
545
|
+
```javascript
|
|
546
|
+
timeoutMS: 120000,
|
|
547
|
+
timeoutFactor: 2.5,
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
6. **Schedule full runs nightly, use incremental on PRs.**
|
|
551
|
+
|
|
552
|
+
### Prioritizing Which Modules to Mutate
|
|
553
|
+
|
|
554
|
+
Use this decision matrix:
|
|
555
|
+
|
|
556
|
+
| Factor | Weight | Assessment |
|
|
557
|
+
|--------|--------|------------|
|
|
558
|
+
| Business criticality | High | Payment, auth, data integrity |
|
|
559
|
+
| Bug history | High | Files with frequent bug fixes |
|
|
560
|
+
| Complexity (cyclomatic) | Medium | High-complexity functions |
|
|
561
|
+
| Change frequency | Medium | Frequently modified files |
|
|
562
|
+
| Coverage gap | Medium | High line coverage but untested logic |
|
|
563
|
+
| User-facing impact | High | Features affecting end users directly |
|
|
564
|
+
|
|
565
|
+
Start with the intersection of "high business criticality" and "high code coverage" --
|
|
566
|
+
these are the modules where mutation testing adds the most value (you think they are
|
|
567
|
+
well-tested but may not be).
|
|
568
|
+
|
|
569
|
+
### Common Pitfalls
|
|
570
|
+
|
|
571
|
+
1. **Trying to reach 100% mutation score** -- diminishing returns past 80%. Some mutants
|
|
572
|
+
are equivalent or test trivial code.
|
|
573
|
+
|
|
574
|
+
2. **Running mutation testing on the entire codebase at once** -- start with critical
|
|
575
|
+
modules and expand gradually.
|
|
576
|
+
|
|
577
|
+
3. **Ignoring survived mutants without analysis** -- each survived mutant is a potential
|
|
578
|
+
bug your tests would miss. Triage them.
|
|
579
|
+
|
|
580
|
+
4. **Not using incremental mode** -- full runs on every commit waste CI minutes.
|
|
581
|
+
|
|
582
|
+
5. **Mutating generated code** -- exclude auto-generated files, they inflate the
|
|
583
|
+
denominator without adding value.
|
|
584
|
+
|
|
585
|
+
## Disabling Specific Mutators
|
|
586
|
+
|
|
587
|
+
If certain mutation operators produce too many equivalent mutants for your codebase:
|
|
588
|
+
|
|
589
|
+
```javascript
|
|
590
|
+
// stryker.config.mjs
|
|
591
|
+
export default {
|
|
592
|
+
// Disable specific mutators
|
|
593
|
+
mutator: {
|
|
594
|
+
excludedMutations: [
|
|
595
|
+
'StringLiteral', // Skip string mutations if you have many display strings
|
|
596
|
+
'ObjectLiteral', // Skip object literal removal
|
|
597
|
+
],
|
|
598
|
+
},
|
|
599
|
+
};
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
Available mutator group names:
|
|
603
|
+
- `ArithmeticOperator`, `ArrayDeclaration`, `AssignmentOperator`
|
|
604
|
+
- `BlockStatement`, `BooleanLiteral`, `ConditionalExpression`
|
|
605
|
+
- `EqualityOperator`, `LogicalOperator`, `MethodExpression`
|
|
606
|
+
- `ObjectLiteral`, `OptionalChaining`, `Regex`
|
|
607
|
+
- `StringLiteral`, `UnaryOperator`, `UpdateOperator`
|
|
608
|
+
|
|
609
|
+
## Interpreting Results: Worked Example
|
|
610
|
+
|
|
611
|
+
Given this source file:
|
|
612
|
+
|
|
613
|
+
```typescript
|
|
614
|
+
// src/services/discount.ts
|
|
615
|
+
export function applyDiscount(price: number, discountPct: number): number {
|
|
616
|
+
if (discountPct < 0 || discountPct > 100) {
|
|
617
|
+
throw new Error('Invalid discount percentage');
|
|
618
|
+
}
|
|
619
|
+
if (discountPct === 0) {
|
|
620
|
+
return price;
|
|
621
|
+
}
|
|
622
|
+
return price - (price * discountPct) / 100;
|
|
623
|
+
}
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
And this test:
|
|
627
|
+
|
|
628
|
+
```typescript
|
|
629
|
+
it('should apply 20% discount', () => {
|
|
630
|
+
expect(applyDiscount(100, 20)).toBe(80);
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
it('should reject negative discount', () => {
|
|
634
|
+
expect(() => applyDiscount(100, -5)).toThrow();
|
|
635
|
+
});
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
**Stryker results:**
|
|
639
|
+
- `discountPct < 0` -> `discountPct <= 0`: **SURVIVED** (no test for `discountPct === 0` boundary)
|
|
640
|
+
- `discountPct > 100` -> `discountPct >= 100`: **SURVIVED** (no test for `discountPct === 100`)
|
|
641
|
+
- `price * discountPct` -> `price / discountPct`: **KILLED** by the 20% test
|
|
642
|
+
- `price - (...)` -> `price + (...)`: **KILLED** by the 20% test
|
|
643
|
+
- `discountPct === 0` -> `discountPct !== 0`: **SURVIVED** (no test for zero discount)
|
|
644
|
+
|
|
645
|
+
**Tests to add:**
|
|
646
|
+
|
|
647
|
+
```typescript
|
|
648
|
+
it('should return full price for 0% discount', () => {
|
|
649
|
+
expect(applyDiscount(100, 0)).toBe(100);
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
it('should apply 100% discount', () => {
|
|
653
|
+
expect(applyDiscount(100, 100)).toBe(0);
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
it('should reject discount over 100', () => {
|
|
657
|
+
expect(() => applyDiscount(100, 101)).toThrow();
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
it('should reject exactly 0 boundary correctly', () => {
|
|
661
|
+
// This is valid -- 0% discount should NOT throw
|
|
662
|
+
expect(applyDiscount(50, 0)).toBe(50);
|
|
663
|
+
});
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
## Beyond Stryker: Other Ecosystems
|
|
667
|
+
|
|
668
|
+
### Java: PIT (pitest)
|
|
669
|
+
|
|
670
|
+
```xml
|
|
671
|
+
<!-- pom.xml -->
|
|
672
|
+
<plugin>
|
|
673
|
+
<groupId>org.pitest</groupId>
|
|
674
|
+
<artifactId>pitest-maven</artifactId>
|
|
675
|
+
<version>1.15.0</version>
|
|
676
|
+
<configuration>
|
|
677
|
+
<targetClasses>
|
|
678
|
+
<param>com.example.service.*</param>
|
|
679
|
+
</targetClasses>
|
|
680
|
+
<mutationThreshold>70</mutationThreshold>
|
|
681
|
+
</configuration>
|
|
682
|
+
</plugin>
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
```bash
|
|
686
|
+
mvn org.pitest:pitest-maven:mutationCoverage
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
### Python: mutmut
|
|
690
|
+
|
|
691
|
+
```bash
|
|
692
|
+
pip install mutmut
|
|
693
|
+
|
|
694
|
+
# Run mutation testing
|
|
695
|
+
mutmut run --paths-to-mutate=src/
|
|
696
|
+
|
|
697
|
+
# View results
|
|
698
|
+
mutmut results
|
|
699
|
+
mutmut html # Generate HTML report
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
### Rust: cargo-mutants
|
|
703
|
+
|
|
704
|
+
```bash
|
|
705
|
+
cargo install cargo-mutants
|
|
706
|
+
|
|
707
|
+
# Run mutation testing
|
|
708
|
+
cargo mutants
|
|
709
|
+
|
|
710
|
+
# Target specific modules
|
|
711
|
+
cargo mutants -- --package my-crate -f src/parser.rs
|
|
712
|
+
|
|
713
|
+
# Skip slow tests
|
|
714
|
+
cargo mutants --timeout 60
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
### When to Use Framework-Specific Tools
|
|
718
|
+
|
|
719
|
+
| Language | Tool | Best For |
|
|
720
|
+
|----------|------|----------|
|
|
721
|
+
| TypeScript/JavaScript | Stryker | Full ecosystem, Vitest/Jest/Mocha support |
|
|
722
|
+
| Java/Kotlin | PIT (pitest) | Maven/Gradle integration, fast bytecode mutation |
|
|
723
|
+
| Python | mutmut | Simple setup, good defaults |
|
|
724
|
+
| Rust | cargo-mutants | Cargo integration, type-system-aware mutations |
|
|
725
|
+
| C# | Stryker.NET | .NET ecosystem, NUnit/xUnit/MSTest |
|
|
726
|
+
| Scala | Stryker4s | sbt integration |
|
|
727
|
+
|
|
728
|
+
**General rule**: Use the tool native to your build ecosystem. Stryker covers JS/TS/.NET/Scala.
|
|
729
|
+
PIT dominates Java. mutmut and cargo-mutants are the pragmatic choices for Python and Rust.
|
|
730
|
+
|
|
731
|
+
## Quick Reference
|
|
732
|
+
|
|
733
|
+
### Setup Checklist
|
|
734
|
+
|
|
735
|
+
- [ ] Install Stryker and test-runner plugin
|
|
736
|
+
- [ ] Create `stryker.config.mjs` with targeted `mutate` globs
|
|
737
|
+
- [ ] Enable `coverageAnalysis: 'perTest'`
|
|
738
|
+
- [ ] Enable `incremental: true` for development
|
|
739
|
+
- [ ] Add `.stryker-cache/` and `.stryker-tmp/` to `.gitignore`
|
|
740
|
+
- [ ] Add `test:mutation` script to package.json
|
|
741
|
+
- [ ] Configure CI workflow (incremental on PRs, full weekly)
|
|
742
|
+
- [ ] Set `thresholds.break` for CI quality gate
|
|
743
|
+
|
|
744
|
+
### Commands Cheat Sheet
|
|
745
|
+
|
|
746
|
+
```bash
|
|
747
|
+
npx stryker init # Interactive setup
|
|
748
|
+
npx stryker run # Full run
|
|
749
|
+
npx stryker run --incremental # Incremental (fast)
|
|
750
|
+
npx stryker run --mutate 'src/core/**' # Target specific files
|
|
751
|
+
npx stryker run --logLevel debug # Debug output
|
|
752
|
+
npx stryker run --concurrency 2 # Limit parallelism
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
### Score Targets
|
|
756
|
+
|
|
757
|
+
| Context | Target Score |
|
|
758
|
+
|---------|-------------|
|
|
759
|
+
| Payment/auth/security code | 80%+ |
|
|
760
|
+
| Core business logic | 70%+ |
|
|
761
|
+
| Utility libraries | 60%+ |
|
|
762
|
+
| General application code | 50%+ |
|
|
763
|
+
| Initial adoption (baseline) | Any improvement |
|
|
764
|
+
|
|
765
|
+
## Related Skills
|
|
766
|
+
|
|
767
|
+
- `unit-testing` - Write and improve the unit tests that mutation testing evaluates
|
|
768
|
+
- `qa-engineer` - Overall test strategy and quality metrics
|
|
769
|
+
- `e2e-testing` - E2E and visual regression tests
|