suggest-testability-improvements

Suggests testability improvements for PHP code. Provides DI refactoring suggestions, mock opportunities, interface extraction, testing strategy recommendations.

59 stars

Best use case

suggest-testability-improvements is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Suggests testability improvements for PHP code. Provides DI refactoring suggestions, mock opportunities, interface extraction, testing strategy recommendations.

Teams using suggest-testability-improvements should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/suggest-testability-improvements/SKILL.md --create-dirs "https://raw.githubusercontent.com/dykyi-roman/awesome-claude-code/main/skills/suggest-testability-improvements/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/suggest-testability-improvements/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How suggest-testability-improvements Compares

Feature / Agentsuggest-testability-improvementsStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Suggests testability improvements for PHP code. Provides DI refactoring suggestions, mock opportunities, interface extraction, testing strategy recommendations.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

Related Guides

SKILL.md Source

# Testability Improvement Suggestions

Provide actionable suggestions to improve code testability.

## Improvement Categories

### 1. Extract Interface for Dependencies

```php
// BEFORE: Concrete dependency
class PaymentProcessor
{
    public function __construct(
        private StripeGateway $gateway, // Concrete class
    ) {}
}

// Test problem: Must mock StripeGateway internals

// AFTER: Interface dependency
interface PaymentGatewayInterface
{
    public function charge(Money $amount, PaymentMethod $method): PaymentResult;
}

class PaymentProcessor
{
    public function __construct(
        private PaymentGatewayInterface $gateway,
    ) {}
}

// Test benefit: Simple mock implementation
$mockGateway = $this->createMock(PaymentGatewayInterface::class);
$mockGateway->method('charge')->willReturn(PaymentResult::success());
```

### 2. Inject Time/Random Dependencies

```php
// BEFORE: Hard to test time-based logic
class TokenGenerator
{
    public function generate(): Token
    {
        return new Token(
            bin2hex(random_bytes(32)),
            new DateTime('+1 hour'),
        );
    }
}

// AFTER: Injectable dependencies
interface ClockInterface
{
    public function now(): DateTimeImmutable;
}

interface RandomGeneratorInterface
{
    public function bytes(int $length): string;
}

class TokenGenerator
{
    public function __construct(
        private ClockInterface $clock,
        private RandomGeneratorInterface $random,
    ) {}

    public function generate(): Token
    {
        return new Token(
            bin2hex($this->random->bytes(32)),
            $this->clock->now()->modify('+1 hour'),
        );
    }
}

// Test with frozen time
$clock = new FrozenClock(new DateTimeImmutable('2024-01-01 12:00:00'));
$random = new FixedRandom('0123456789abcdef...');
$generator = new TokenGenerator($clock, $random);
$token = $generator->generate();
// Now assertions are deterministic
```

### 3. Create Test Builders

```php
// BEFORE: Tedious test setup
public function testOrderProcessing(): void
{
    $customer = new Customer();
    $customer->setId(1);
    $customer->setName('John');
    $customer->setEmail('john@example.com');
    $customer->setStatus('active');

    $product = new Product();
    $product->setId(1);
    $product->setName('Widget');
    $product->setPrice(new Money(1000, 'USD'));

    $order = new Order();
    $order->setCustomer($customer);
    $order->addItem(new OrderItem($product, 2));
    // ... 20 more lines
}

// AFTER: Fluent builder
public function testOrderProcessing(): void
{
    $order = OrderBuilder::create()
        ->withCustomer(CustomerBuilder::active()->build())
        ->withItem('Widget', 1000, quantity: 2)
        ->build();

    $result = $this->processor->process($order);

    $this->assertTrue($result->isSuccessful());
}
```

### 4. Separate Pure Logic from I/O

```php
// BEFORE: Logic mixed with I/O
class PricingService
{
    public function calculateOrderPrice(int $orderId): Money
    {
        $order = $this->repository->find($orderId); // I/O
        $customer = $this->customerRepo->find($order->getCustomerId()); // I/O
        $rates = $this->taxApi->getRates($customer->getCountry()); // I/O

        // Business logic
        $subtotal = $this->calculateSubtotal($order);
        $discount = $this->applyDiscount($customer, $subtotal);
        $tax = $this->calculateTax($subtotal, $rates);

        return $subtotal->subtract($discount)->add($tax);
    }
}

// AFTER: Pure calculator
final readonly class OrderPriceCalculator
{
    public function calculate(
        Order $order,
        Customer $customer,
        TaxRates $taxRates,
    ): Money {
        $subtotal = $this->calculateSubtotal($order);
        $discount = $this->applyDiscount($customer, $subtotal);
        $tax = $this->calculateTax($subtotal, $taxRates);

        return $subtotal->subtract($discount)->add($tax);
    }
}

// I/O in thin service
final class PricingService
{
    public function __construct(
        private OrderRepository $orderRepo,
        private CustomerRepository $customerRepo,
        private TaxApiInterface $taxApi,
        private OrderPriceCalculator $calculator,
    ) {}

    public function calculateOrderPrice(int $orderId): Money
    {
        $order = $this->orderRepo->find($orderId);
        $customer = $this->customerRepo->find($order->getCustomerId());
        $rates = $this->taxApi->getRates($customer->getCountry());

        return $this->calculator->calculate($order, $customer, $rates);
    }
}

// Now calculator is easily testable without mocks
```

### 5. Use Repository Pattern for Data Access

```php
// BEFORE: Direct database access
class UserService
{
    public function __construct(private PDO $pdo) {}

    public function findActive(): array
    {
        $stmt = $this->pdo->query("SELECT * FROM users WHERE active = 1");
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
}

// AFTER: Repository interface
interface UserRepositoryInterface
{
    /** @return User[] */
    public function findActive(): array;
    public function findById(int $id): ?User;
    public function save(User $user): void;
}

class UserService
{
    public function __construct(
        private UserRepositoryInterface $repository,
    ) {}

    public function findActive(): array
    {
        return $this->repository->findActive();
    }
}

// In-memory implementation for tests
class InMemoryUserRepository implements UserRepositoryInterface
{
    private array $users = [];

    public function findActive(): array
    {
        return array_filter($this->users, fn($u) => $u->isActive());
    }

    public function givenUser(User $user): void
    {
        $this->users[$user->getId()] = $user;
    }
}
```

### 6. Create Test Doubles

```php
// Fake implementation for testing
final class FakeEmailSender implements EmailSenderInterface
{
    private array $sentEmails = [];

    public function send(Email $email): void
    {
        $this->sentEmails[] = $email;
    }

    public function getSentEmails(): array
    {
        return $this->sentEmails;
    }

    public function assertEmailSentTo(string $address): void
    {
        foreach ($this->sentEmails as $email) {
            if ($email->getTo() === $address) {
                return;
            }
        }
        throw new AssertionError("No email sent to $address");
    }
}
```

### 7. Parameter Objects for Complex Methods

```php
// BEFORE: Many parameters, hard to mock
public function createOrder(
    int $customerId,
    array $items,
    string $shippingMethod,
    string $paymentMethod,
    ?string $couponCode,
    ?string $notes,
): Order {}

// AFTER: DTO with builder
final readonly class CreateOrderRequest
{
    public function __construct(
        public int $customerId,
        public array $items,
        public string $shippingMethod,
        public string $paymentMethod,
        public ?string $couponCode = null,
        public ?string $notes = null,
    ) {}
}

// Test with builder
$request = CreateOrderRequestBuilder::create()
    ->forCustomer(1)
    ->withItem('SKU-001', 2)
    ->withShipping('express')
    ->build();
```

## Implementation Priority

| Improvement | Impact | Effort |
|-------------|--------|--------|
| Extract interface | High | Low |
| Inject time/random | High | Medium |
| Create test builders | Medium | Medium |
| Separate pure logic | High | High |
| Repository pattern | High | Medium |

## Output Format

```markdown
### Testability Improvement: [Description]

**Location:** `file.php:line`
**Type:** [Extract Interface|Inject Dependency|Create Builder|...]
**Impact:** High/Medium/Low

**Current Problem:**
[Why current code is hard to test]

**Suggested Improvement:**
```php
// Improved code
```

**Implementation Steps:**
1. Create interface XxxInterface
2. Update class to depend on interface
3. Create test double/mock
4. Update test

**Testing Benefit:**
[How this makes testing easier]
```

Related Skills

suggest-simplification

59
from dykyi-roman/awesome-claude-code

Suggests code simplification opportunities. Identifies extract method candidates, complex expressions, redundant code, refactoring opportunities.

yii-knowledge

59
from dykyi-roman/awesome-claude-code

Yii framework knowledge base. Provides Yii3 modular architecture, DDD integration, PSR-7/PSR-15 compliance, persistence, DI, security (RBAC, auth), event system (PSR-14), queue/jobs, infrastructure components (cache, rate limiter, HTTP client), testing, and antipatterns for Yii PHP projects.

troubleshooting-template

59
from dykyi-roman/awesome-claude-code

Generates troubleshooting guides and FAQ sections for PHP projects. Creates problem-solution documentation.

trace-request-lifecycle

59
from dykyi-roman/awesome-claude-code

Traces full request lifecycle from Router through Middleware, Controller, UseCase, Repository to Response. Documents HTTP methods, routes, middleware stack, response codes, and error handling paths.

trace-data-transformation

59
from dykyi-roman/awesome-claude-code

Maps data transformation chains — Request DTO to Command to Entity to Response DTO. Identifies mappers, serializers, type conversions, and data loss points across layer boundaries.

testing-knowledge

59
from dykyi-roman/awesome-claude-code

Testing knowledge base for PHP 8.4 projects. Provides testing pyramid, AAA pattern, naming conventions, isolation principles, DDD testing guidelines, and PHPUnit patterns.

task-progress-knowledge

59
from dykyi-roman/awesome-claude-code

TaskCreate pattern guidelines for progress tracking in coordinator agents

symfony-knowledge

59
from dykyi-roman/awesome-claude-code

Symfony framework knowledge base. Provides architecture, DDD integration, persistence, DI, security, messenger, workflow, events, infrastructure components, testing, and antipatterns for Symfony PHP projects.

stability-patterns-knowledge

59
from dykyi-roman/awesome-claude-code

Stability Patterns knowledge base. Provides patterns, antipatterns, and PHP-specific guidelines for Circuit Breaker, Retry, Rate Limiter, Bulkhead, and resilience audits.

solid-knowledge

59
from dykyi-roman/awesome-claude-code

SOLID principles knowledge base for PHP 8.4 projects. Provides quick reference for SRP, OCP, LSP, ISP, DIP with detection patterns, PHP examples, and antipattern identification. Use for architecture audits and code quality reviews.

scan-codebase-structure

59
from dykyi-roman/awesome-claude-code

Scans directory tree to identify architectural layers (Domain, Application, Infrastructure, Presentation), detect framework (Symfony, Laravel, custom), count files per layer, and build project structure map.

scalability-knowledge

59
from dykyi-roman/awesome-claude-code

Scalability knowledge base. Provides vertical vs horizontal scaling, stateless design, session management, connection pooling, capacity planning, and PHP-FPM tuning for scalability audits.