From test-writing
Generates Shopware-compliant PHPUnit unit tests for PHP classes in tests/unit/, skipping coverage-excluded or trivial ones, validating with PHPStan, ECS, and PHPUnit.
npx claudepluginhub shopwarelabs/ai-coding-tools --plugin test-writingThis skill is limited to using the following tools:
Generate Shopware-compliant PHPUnit unit tests that pass PHPStan and PHPUnit validation.
references/category-detection.mdreferences/common-patterns.mdreferences/essential-rules.mdreferences/output-format.mdreferences/shopware-stubs.mdreferences/test-requirement-rules.mdreferences/validation-error-mapping.mdtemplates/category-a-dto.mdtemplates/category-b-service.mdtemplates/category-c-flow.mdtemplates/category-d-dal.mdtemplates/category-e-exception.mdGenerates design tokens/docs from CSS/Tailwind/styled-components codebases, audits visual consistency across 10 dimensions, detects AI slop in UI.
Records polished WebM UI demo videos of web apps using Playwright with cursor overlay, natural pacing, and three-phase scripting. Activates for demo, walkthrough, screen recording, or tutorial requests.
Delivers idiomatic Kotlin patterns for null safety, immutability, sealed classes, coroutines, Flows, extensions, DSL builders, and Gradle DSL. Use when writing, reviewing, refactoring, or designing Kotlin code.
Generate Shopware-compliant PHPUnit unit tests that pass PHPStan and PHPUnit validation.
Write ONLY to:
tests/unit/** - Unit test filesNEVER write to:
src/** - Source code (read-only)tests/integration/** - Out of scopeUse ONLY MCP tools for PHP validation (NEVER Bash commands):
| Instead of (Bash) | Use (MCP) |
|---|---|
vendor/bin/phpstan | mcp__plugin_dev-tooling_php-tooling__phpstan_analyze |
vendor/bin/phpunit | mcp__plugin_dev-tooling_php-tooling__phpunit_run |
vendor/bin/ecs | mcp__plugin_dev-tooling_php-tooling__ecs_check / ecs_fix |
composer phpstan:* | MCP equivalent |
MCP tools handle environment detection (native/docker/vagrant/ddev) automatically.
Before analyzing the source class, check if the project's phpunit.xml.dist (or phpunit.xml) excludes it from coverage. Files excluded from coverage do not need unit tests.
phpunit.xml.dist from the project root<exclude> rules inside the <coverage> or <source> section<directory suffix="X">path</directory> — excluded if file is under path AND filename ends with X<file>path/to/File.php</file> — excluded if relative path matches exactlyskip_type: coverage_excluded and reason: "Source file excluded from coverage by phpunit.xml.dist (<matched-rule>)"If phpunit.xml.dist is not found, skip this step.
Before generating any test, evaluate if the class/method requires one.
Quick check: Does the method body contain ONLY return <literal|constant|property|passthrough-new|delegation>?
skip_type: no_logic and reason describing the pattern (e.g., "Pure accessor - no logic to test")For detailed rules on what to test vs skip, see test-requirement-rules.md.
Read the target class to determine:
Use the decision tree to select the appropriate category:
Has constructor dependencies?
├── No → Is it an Exception class?
│ ├── Yes → Category E
│ └── No → Category A (DTO)
└── Yes → Uses EntityRepository?
├── Yes → Category D (DAL)
└── No → Implements EventSubscriberInterface or FlowAction?
├── Yes → Category C (Flow/Event)
└── No → Category B (Service)
For detailed category criteria, see category-detection.md.
Apply these mandatory conventions when generating tests.
| Rule | Requirement |
|---|---|
| File location | tests/unit/ mirroring src/ path |
| Class attribute | #[CoversClass(TargetClass::class)] required |
| Assertions | Use static:: not $this-> |
| Base class | Extend PHPUnit\Framework\TestCase |
| Method naming | test + Action + Condition + ExpectedResult |
| Attribute order | PHPDoc -> DataProvider -> TestDox -> method |
| One behavior | NO conditionals in tests |
TestDox MUST be a predicate phrase starting with an action verb:
StaticEntityRepository, StaticSystemConfigService, GeneratorFor complete rules, see essential-rules.md.
Based on category from Phase 1:
| Category | Template |
|---|---|
| A (DTO) | category-a-dto.md |
| B (Service) | category-b-service.md |
| C (Flow/Event) | category-c-flow.md |
| D (DAL) | category-d-dal.md |
| E (Exception) | category-e-exception.md |
{Module} - Core module (e.g., Content, Checkout, System){Submodule} - Submodule path (e.g., Product, Cart\LineItem){TargetClass} - Class name being tested{Entity} - Entity name for DAL tests{Method} - Method name being tested{Expected} - Expected outcome description{Condition} - Condition description{Exception} - Exception class nameWrite to correct location: tests/unit/{path matching src}/{ClassName}Test.php
CRITICAL: Use ONLY MCP tools for validation. NEVER use shell commands.
Prerequisite: The dev-tooling plugin must be installed (provides php-tooling MCP server). If unavailable, proceed to Phase 5 with status PARTIAL.
- [ ] PHPStan passes (0 errors)
- [ ] PHPUnit passes (all tests green)
- [ ] ECS passes (code style)
{
"paths": ["tests/unit/Path/To/GeneratedTest.php"],
"error_format": "json"
}
Zero errors = pass.
Apply fixes for common errors. See validation-error-mapping.md.
{
"paths": ["tests/unit/Path/To/GeneratedTest.php"],
"output_format": "result-only"
}
All tests passing = success. If tests fail, re-run without output_format to get failure details for Step 4.
Apply fixes for common failures. See validation-error-mapping.md.
Check for violations, then apply fixes if needed.
Loop through Steps 1-5 until all validations pass.
Maximum iterations: Stop after 3 failed attempts and proceed to Phase 5.
For output format and examples, see output-format.md.
| Condition | Status | skip_type |
|---|---|---|
| All validations pass | SUCCESS | — |
| Test generated, validation issues remain after 3 iterations | PARTIAL | — |
| File excluded from coverage in phpunit.xml.dist | SKIPPED | coverage_excluded |
| No testable logic (per Test Requirement Rules) | SKIPPED | no_logic |
| Invalid input (not a PHP class, file not found) | FAILED | — |
For detailed patterns and techniques, consult:
Category-specific test generation templates in templates/: