# Testing and Code Quality: PHPUnit, Pest, PHPStan and Rector — PHP

Source: https://www.skillbyai.com/en/php/x-testing

> Test PHP code and automate quality checks and upgrades.

## A quality toolchain

**PHPUnit** is the standard testing framework: test classes extend `TestCase`, methods are marked with the `#[Test]` attribute or a `test` prefix, and assertions such as `assertSame` (strict) check results; data providers (`#[DataProvider]`) run one test with many inputs, and test doubles (`createMock`, `createStub`) replace collaborators. **Pest** offers a more concise, expressive syntax on top of PHPUnit and is popular in the Laravel community. Frameworks add HTTP testing helpers and database refreshing. Complement tests with **static analysis** (PHPStan or Psalm), **coding style** tools (PHP-CS-Fixer), and **Rector**, which applies automated refactorings and version upgrades (for example converting code to use constructor promotion, readonly properties or newer syntax). **Infection** provides mutation testing. Run everything in CI on every pull request: `composer validate`, `composer audit`, coding style, PHPStan, tests with coverage. Together they make large PHP codebases maintainable and upgrades between PHP versions routine rather than risky.

## A PHPUnit test with a data provider

Strict assertions and one test method covering several cases.

```php
<?php
declare(strict_types=1);

namespace Tests\Unit;

use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use Shop\Shipping\ShippingCalculator;

final class ShippingCalculatorTest extends TestCase
{
    public static function cases(): iterable
    {
        yield 'below threshold, standard' => [99_999, false, 4_900];
        yield 'below threshold, express'  => [99_999, true, 9_900];
        yield 'at threshold is free'      => [100_000, true, 0];
    }

    #[Test]
    #[DataProvider('cases')]
    public function feeDependsOnTotalAndSpeed(int $totalPaise, bool $express, int $expected): void
    {
        $calculator = new ShippingCalculator();
        self::assertSame($expected, $calculator->feePaise($totalPaise, $express));
    }
}

// vendor/bin/phpunit
// vendor/bin/phpstan analyse --level=8
// vendor/bin/rector process src --dry-run      (preview automated upgrades)
```

## assertSame over assertEquals

`assertEquals` compares loosely, so `"1"` equals `1`. `assertSame` checks type and value, matching PHP's `===` and catching type bugs.

**Quiz:** What is Rector used for?

- [ ] Serving HTTP requests
- [ ] Database migrations
- [x] Automated refactoring and upgrading of PHP code
- [ ] Caching opcodes

*Answer:* Automated refactoring and upgrading of PHP code. Rector rewrites code automatically, for example to adopt newer PHP syntax.
