ikvasnica / phpstan-clean-test
PHPStan 扩展,提供严格的代码规范以优化测试代码。
Requires
- php: >=8.1
- nette/utils: ~3.0
- nikic/php-parser: ^4.3
- phpstan/phpstan: ^1.10
Requires (Dev)
- ergebnis/phpstan-rules: ^2.1.0
- php-coveralls/php-coveralls: 2.*
- phpstan/phpstan-strict-rules: ^1.5.1
- phpunit/phpunit: ^9.0.0
- symplify/easy-coding-standard: ^12.0.6
Suggests
- ergebnis/phpstan-rules: Even more PHPStan strict and opinionated rules.
- phpstan/phpstan-deprecation-rules: Rules for disallowing deprecation code.
- phpstan/phpstan-phpunit: PHPStan extension and rules to integrate with PHPUnit testing framework.
- phpstan/phpstan-strict-rules: Strict and opinionated PHPStan rules.
README
此扩展为 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
一起使用的规则提供支持
ikvasnica\PHPStan\Rules\UnitExtendsFromTestCaseRule
ikvasnica\PHPStan\Rules\DisallowSetupAndConstructorRule
ikvasnica\PHPStan\Rules\AssertSameOverAssertEqualsRule
ikvasnica\PHPStan\Rules\StaticAssertOverThisAndStaticRule
ikvasnica\PHPStan\Rules\NoNullableArgumentRule
UnitExtendsFromTestCaseRule
此规则强制您在单元测试中仅从允许的类扩展(默认:PHPUnit\Framework\TestCase
)。
为什么
- 它防止开发人员,例如在单元测试中使用依赖注入容器(
$this->getContainer()
)和其他工具从集成/功能测试中。 - 当子类满足“是”关系时,您应该仅从类扩展。也就是说,如果您只需要父类功能的一个子集,您应该使用组合而非继承(例如通过 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
单元测试中不能声明方法 __construct
或 setUp
。
为什么:每个测试场景都应该创建自己的依赖。方法 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 } }