Internal sub-skill of phpunit-unit-test-writing orchestrator. Not user-facing — invoked only via Skill(test-writing:phpunit-unit-test-generation) from the orchestrator.
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 scopeBefore 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 references/test-requirement-rules.md.
Read the target class to determine:
@deprecated tags, Feature::triggerDeprecationOrThrow(), Feature::silent(), Feature::callSilentIfInactive() (see references/deprecation-guards.md)#[Package('...')] value the covered class carries. The test class carries the same value. When the covered class carries none, take the value from the nearest src/ directory the test path mirrors, walking up until one exists and using the value its .php files carry. When that yields no value, emit no #[Package] and report that in the Phase 5 report's Package field (none, per references/output-format.md) rather than guessing a value.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 references/category-detection.md.
Apply these mandatory conventions when generating tests.
| Rule | Requirement |
|------|-------------|
| File location | tests/unit/ mirroring src/ path |
| Class attributes | #[Package('...')] then #[CoversClass(TargetClass::class)], both 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 createStub vs createMock selection, see references/mocking-patterns.md.
For complete rules, see references/essential-rules.md.
Based on category from Phase 1:
| Category | Template | |----------|----------| | A (DTO) | templates/category-a-dto.md | | B (Service) | templates/category-b-service.md | | C (Flow/Event) | templates/category-c-flow.md | | D (DAL) | templates/category-d-dal.md | | E (Exception) | templates/category-e-exception.md |
For data provider and decoration contract patterns, see references/common-patterns.md.
{package} - #[Package] value from Phase 1 Step 3 (e.g., framework, discovery){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
- [ ] 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 references/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 references/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 references/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 | — |
none when derivation yielded no value)For detailed patterns and techniques, consult:
Category-specific test generation templates in templates/:
npx skills add shopwareLabs/phpunit-unit-test-generation下载完整 Skill 目录,包含 SKILL.md 及所有相关文件
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer