acc-create-unit-test
1
总安装量
1
周安装量
#46465
全站排名
安装命令
npx skills add https://github.com/dykyi-roman/awesome-claude-code --skill acc-create-unit-test
Agent 安装分布
opencode
1
claude-code
1
Skill 文档
Unit Test Generator
Generates PHPUnit 11+ unit tests for PHP 8.5 classes.
Characteristics
- Isolated â no external dependencies (DB, HTTP, filesystem)
- Fast â executes in <100ms
- Focused â one behavior per test
- AAA Pattern â Arrange-Act-Assert structure
- Self-documenting â descriptive test names
Template
<?php
declare(strict_types=1);
namespace Tests\Unit\{Namespace};
use {FullyQualifiedClassName};
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\TestCase;
#[Group('unit')]
#[CoversClass({ClassName}::class)]
final class {ClassName}Test extends TestCase
{
private {ClassName} $sut;
protected function setUp(): void
{
$this->sut = new {ClassName}(/* dependencies */);
}
public function test_{method}_{scenario}_{expected}(): void
{
// Arrange
{arrange_code}
// Act
{act_code}
// Assert
{assert_code}
}
}
Naming Convention
test_{method}_{scenario}_{expected}
| Part | Description | Example |
|---|---|---|
{method} |
Method under test | calculate_total |
{scenario} |
Input condition | with_discount |
{expected} |
Expected outcome | returns_reduced_price |
Examples:
test_confirm_when_pending_changes_status_to_confirmed
test_create_with_invalid_email_throws_exception
test_equals_with_same_value_returns_true
test_add_item_increases_total
Test Patterns by Component
Value Object Tests
#[Group('unit')]
#[CoversClass(Email::class)]
final class EmailTest extends TestCase
{
// Creation - valid
public function test_creates_with_valid_email(): void
{
$email = new Email('user@example.com');
self::assertSame('user@example.com', $email->value);
}
// Validation - invalid
public function test_throws_for_empty_value(): void
{
$this->expectException(InvalidArgumentException::class);
new Email('');
}
public function test_throws_for_invalid_format(): void
{
$this->expectException(InvalidArgumentException::class);
new Email('not-an-email');
}
// Equality
public function test_equals_returns_true_for_same_value(): void
{
$email1 = new Email('user@example.com');
$email2 = new Email('user@example.com');
self::assertTrue($email1->equals($email2));
}
public function test_equals_returns_false_for_different_value(): void
{
$email1 = new Email('user@example.com');
$email2 = new Email('other@example.com');
self::assertFalse($email1->equals($email2));
}
}
Entity Tests
#[Group('unit')]
#[CoversClass(Order::class)]
final class OrderTest extends TestCase
{
private Order $order;
protected function setUp(): void
{
$this->order = new Order(
OrderId::fromString('order-123'),
CustomerId::fromString('customer-456')
);
}
// Identity
public function test_has_unique_identity(): void
{
self::assertSame('order-123', $this->order->id()->toString());
}
// Initial state
public function test_is_pending_when_created(): void
{
self::assertTrue($this->order->isPending());
}
// State transitions - valid
public function test_confirm_changes_status_to_confirmed(): void
{
$this->order->addItem(ProductMother::book(), 1);
$this->order->confirm();
self::assertTrue($this->order->isConfirmed());
}
// State transitions - invalid
public function test_confirm_throws_when_already_confirmed(): void
{
$this->order->addItem(ProductMother::book(), 1);
$this->order->confirm();
$this->expectException(DomainException::class);
$this->order->confirm();
}
// Business rules
public function test_add_item_increases_total(): void
{
$this->order->addItem(ProductMother::withPrice(Money::EUR(100)), 2);
self::assertEquals(Money::EUR(200), $this->order->total());
}
// Domain events
public function test_records_order_confirmed_event(): void
{
$this->order->addItem(ProductMother::book(), 1);
$this->order->confirm();
$events = $this->order->releaseEvents();
self::assertCount(1, $events);
self::assertInstanceOf(OrderConfirmedEvent::class, $events[0]);
}
}
Domain Service Tests
#[Group('unit')]
#[CoversClass(TransferMoneyService::class)]
final class TransferMoneyServiceTest extends TestCase
{
private TransferMoneyService $service;
private InMemoryAccountRepository $repository;
private CollectingEventDispatcher $dispatcher;
protected function setUp(): void
{
$this->repository = new InMemoryAccountRepository();
$this->dispatcher = new CollectingEventDispatcher();
$this->service = new TransferMoneyService(
$this->repository,
$this->dispatcher
);
}
public function test_transfers_money_between_accounts(): void
{
// Arrange
$source = AccountMother::withBalance(Money::EUR(1000));
$target = AccountMother::withBalance(Money::EUR(500));
$this->repository->save($source);
$this->repository->save($target);
// Act
$this->service->transfer(
$source->id(),
$target->id(),
Money::EUR(300)
);
// Assert
$updatedSource = $this->repository->findById($source->id());
$updatedTarget = $this->repository->findById($target->id());
self::assertEquals(Money::EUR(700), $updatedSource->balance());
self::assertEquals(Money::EUR(800), $updatedTarget->balance());
}
public function test_throws_for_insufficient_funds(): void
{
// Arrange
$source = AccountMother::withBalance(Money::EUR(100));
$target = AccountMother::withBalance(Money::EUR(500));
$this->repository->save($source);
$this->repository->save($target);
// Assert
$this->expectException(InsufficientFundsException::class);
// Act
$this->service->transfer(
$source->id(),
$target->id(),
Money::EUR(300)
);
}
}
Data Providers
use PHPUnit\Framework\Attributes\DataProvider;
#[DataProvider('validEmailsProvider')]
public function test_accepts_valid_formats(string $email): void
{
$vo = new Email($email);
self::assertSame($email, $vo->value);
}
public static function validEmailsProvider(): array
{
return [
'simple' => ['user@example.com'],
'with subdomain' => ['user@mail.example.com'],
'with plus' => ['user+tag@example.com'],
'with dots' => ['first.last@example.com'],
];
}
#[DataProvider('invalidEmailsProvider')]
public function test_rejects_invalid_formats(string $email): void
{
$this->expectException(InvalidArgumentException::class);
new Email($email);
}
public static function invalidEmailsProvider(): array
{
return [
'empty' => [''],
'no at' => ['userexample.com'],
'no domain' => ['user@'],
'spaces' => ['user @example.com'],
];
}
Generation Instructions
-
Analyze the class:
- Identify public methods
- Identify dependencies (constructor parameters)
- Identify value objects (final readonly)
- Identify entities (has id, state changes)
- Identify services (orchestrates, uses repositories)
-
Determine test cases:
- Happy path for each method
- Edge cases (null, empty, boundary)
- Exception paths (validation failures)
- State transitions (for entities)
-
Generate test class:
- Match namespace:
src/Domain/Order/Order.phpâtests/Unit/Domain/Order/OrderTest.php - Add attributes:
#[Group('unit')],#[CoversClass] - Create setUp if shared state needed
- Match namespace:
-
Generate test methods:
- Follow naming convention
- Use AAA structure
- One assertion group per test
-
Add helpers if needed:
- Use existing Mothers/Builders
- Create inline builders for simple cases
Assertions Reference
// Value comparisons
self::assertSame($expected, $actual); // ===
self::assertEquals($expected, $actual); // ==
self::assertTrue($condition);
self::assertFalse($condition);
self::assertNull($value);
self::assertNotNull($value);
// Types
self::assertInstanceOf(ClassName::class, $object);
// Strings
self::assertStringContainsString($needle, $haystack);
self::assertStringStartsWith($prefix, $string);
// Arrays
self::assertCount($expected, $array);
self::assertContains($needle, $array);
self::assertArrayHasKey($key, $array);
// Exceptions
$this->expectException(ExceptionClass::class);
$this->expectExceptionMessage('message');
$this->expectExceptionCode(404);
Usage
Provide:
- Path to class to test
- Or class name and namespace
- Specific methods to focus on (optional)
The generator will:
- Read the source class
- Analyze methods and dependencies
- Generate comprehensive test class
- Include happy path + edge cases + exceptions