ikvasnica/phpstan-clean-test

PHPStan 扩展,提供严格的代码规范以优化测试代码。

安装量: 20,095

依赖项: 0

建议者: 1

安全性: 0

星标: 15

关注者: 3

分支: 0

开放问题: 0

类型:phpstan-extension

0.4 2023-08-23 19:30 UTC

This package is auto-updated.

Last update: 2024-09-23 22:22:54 UTC


README

Continuous Integration Coverage Status Codacy Badge Latest Stable Version License

此扩展为 PHPStan 静态分析工具提供高度意见化和严格的测试用例规则。

安装

运行

$ composer require --dev ikvasnica/phpstan-clean-test

用法

此库提供的所有 规则 都包含在 rules.neon 中。

当您使用 phpstan/extension-installer 时,rules.neon 将自动包含。

否则您需要在您的 phpstan.neon 中包含 rules.neon

# phpstan.neon
includes:
    - vendor/ikvasnica/phpstan-clean-test/rules.neon

规则

此包为以下与 phpstan/phpstan 一起使用的规则提供支持

UnitExtendsFromTestCaseRule

此规则强制您在单元测试中仅从允许的类扩展(默认:PHPUnit\Framework\TestCase)。

为什么

  1. 它防止开发人员,例如在单元测试中使用依赖注入容器($this->getContainer())和其他工具从集成/功能测试中。
  2. 当子类满足“是”关系时,您应该仅从类扩展。也就是说,如果您只需要父类功能的一个子集,您应该使用组合而非继承(例如通过 traits 或 helpers)。

// tests/ExampleTestCase/Unit/UnitExtendsInvalidTest.php
namespace ExampleTestCase\Unit;

final class UnitExtendsInvalidTest extends \Dummy\FunctionalDummyTest {}

// tests/ExampleTestCase/Unit/UnitExtendsUnitTest.php
namespace ExampleTestCase\Unit;

final class UnitExtendsUnitTest extends \PHPUnit\Framework\TestCase {}

默认值

  • 默认情况下,此规则通过检查命名空间(它必须包含字符串 Unit)和类名后缀(它必须以字符串 Test 结尾)来检测单元测试。
  • 以下类允许扩展: PHPUnit\Framework\TestCase

允许扩展的类

如果您想允许扩展额外的类,您可以将其添加到 classesAllowedToBeExtendedInTests 参数中的类名列表。

检测单元测试命名空间

如果您想更改上述描述的命名空间字符串检查,您可以在 unitTestNamespaceContainsString 参数中设置自己的要检查的字符串。

# phpstan.neon
parameters:
    ikvasnica:
        classesAllowedToBeExtendedInTests:
          - MyNamespace\AbstractTest
        unitTestNamespaceContainsString: CustomTestPath

DisallowSetupAndConstructorRule

单元测试中不能声明方法 __constructsetUp

为什么:每个测试场景都应该创建自己的依赖。方法 setUp 对于设置功能测试中的数据库事务等很有用。在单元测试中,您应该将所有准备工作放入测试方法或数据提供者本身。这增加了可读性,并清楚地显示了代码意图。

检测单元测试命名空间

如果您想更改上述描述的命名空间字符串检查,您可以在 unitTestNamespaceContainsString 参数中设置自己的要检查的字符串。

允许 setUp() 方法

如果您真的想使用 setUp() 方法,您可以将其列入白名单,通过将参数 allowSetupInUnitTests 设置为 true

# phpstan.neon
parameters:
    ikvasnica:
        unitTestNamespaceContainsString: CustomTestPath
        allowSetupInUnitTests: true

// tests/ExampleTestCase/Unit/DisallowSetupConstructInvaliTest.php
namespace ExampleTestCase\Unit;

use PHPUnit\Framework\Assert;

final class DisallowSetupConstructInvaliTest extends \PHPUnit\Framework\TestCase
{
    private $something;

    public function __construct($name = null, array $data = [], $dataName = '')
    {
        parent::__construct($name, $data, $dataName);
    }

    protected function setUp(): void
    {
        parent::setUp();

        $this->something = true;
    }

    public function testSomeThing(): void
    {
        Assert::assertTrue($this->something);
    }
}

// tests/ExampleTestCase/Unit/DisallowSetupConstructOkTest.php
namespace ExampleTestCase\Unit;

use PHPUnit\Framework\Assert;

final class DisallowSetupConstructOkTest extends \PHPUnit\Framework\TestCase
{
    public function testSomeThing(): void
    {
        Assert::assertTrue(true);
    }
}

AssertSameOverAssertEqualsRule

在测试中调用 assertEquals 是禁止的,而是使用 assertSame

为什么:当使用 assertEquals 时,不考虑数据类型。另一方面,assertSame 检查两个变量是否具有相同的类型和引用相同的对象。因此,assertEquals 可以用于比较对象或数组,但不能用于标量值。

使用 assertEquals 检查标量值可能会导致意外的行为(例如,assertEquals(null, '') 评估为 true,而 assertSame(null, '') 评估为 false)。

// tests/ExampleTestCase/Unit/InvalidAssertEqualsUses.php
use PHPUnit\Framework\Assert;

$booleanValue = false;
$exception = new Exception('A bad thing has happened.');

Assert::assertEquals(true, $booleanValue);
Assert::assertEquals('exception message', (string) $exception);

// tests/ExampleTestCase/Unit/ValidAsserts.php
use PHPUnit\Framework\Assert;

$booleanValue = false;
$exception = new Exception('A bad thing has happened.');
$emptyArray = [];

Assert::assertTrue($booleanValue);
Assert::assertSame('exception message', (string) $exception);

Assert::assertEquals([], $emptyArray);

StaticAssertOverThisAndStaticRule

在测试中调用 $this->assert*self::assert*static::assert* 是禁止的,应该使用 PHPUnit\Framework\Assert::assert*

原因:当使用 PHPUnit 时,你的测试用例从 \PHPUnit\Framework\TestCase 扩展。断言方法在那里声明为静态,因此动态调用它们是没有意义的。使用 static::assert* 是不推荐的,因为这是对继承的滥用,而断言方法更像是辅助方法。

// tests/ExampleTestCase/Unit/InvalidAssertUsage.php
namespace ExampleTestCase;

final class InvalidAssertUsageTest extends \PHPUnit\Framework\TestCase
{
    public function dummyTest(): void
    {
        // will fail
        $this->assertSame(5, 5);
        $this->assertTrue(false);
        self::assertArrayHasKey(5, [5]);
        static::assertCount(0, []);
        \ExampleTestCase\StaticAssertOverThisAndStaticRule::assertTrue(true);
        InvalidAssertUsageTest::assertTrue(true);
    }
}

// tests/ExampleTestCase/Unit/ValidAssertsUsage.php
namespace ExampleTestCase;

use PHPUnit\Framework\Assert;

final class ValidAssertUsageTest extends \PHPUnit\Framework\TestCase
{
    public function dummyTest(): void
    {
        // Assert::anything is OK
        Assert::assertEquals(5, 5);
        Assert::assertCount(1, [1, 2]);
        Assert::assertTrue(false);
        \PHPUnit\Framework\Assert::assertTrue(true);
    }
}

NoNullableArgumentRule

禁止从数据提供者传递给测试方法的可空参数和无类型参数。

原因:数据提供者中的可空参数是一个代码气味。通常它意味着你在一个测试中测试了两个不同的场景。你应该将测试分成两个场景,即一个用于测试有效的数据输入,另一个用于测试当预期抛出异常时的无效数据。

// tests/ExampleTestCase/Unit/NullableArgumentsInTest.php
namespace ExampleTestCase;

final class NullableArgumentsInTest extends \PHPUnit\Framework\TestCase
{
    public function testSomething($mixedTypeArgument, ?string $stringOrNullArgument): void
    {
        // will fail, because the first argument has no type and the second one is nullable
    }

    /**
     * @test
     * @param string|null $maybeString
     */
    public function someTestMethod(?string $maybeString): void
    {
        // will fail too, because this one is a test method due to the annotation
    }
}

// tests/ExampleTestCase/Unit/ValidArgumentsInTest.php
namespace ExampleTestCase;

final class ValidArgumentsInTest extends \PHPUnit\Framework\TestCase
{
    public function testSomething(string $definitelyString): void
    {
        // this is a way to go!
    }

    public function testSomethingElse(): void
    {
        // no arguments are, of course, allowed
    }

    public function anotherPublicMethod(?bool $maybeTrueOrFalse): void
    {
        // although weird, you may have other non-test public methods with a nullable type as well
    }

    private function testHelperMethod(?string $maybeString): void
    {
        // private and protected methods are allowed to have nullable arguments
    }
}