Skip to content

Support PHP 8 attributes for adding metadata to test classes and test methods as well as tested code units #4502

@sebastianbergmann

Description

@sebastianbergmann

PHPUnit currently supports the following annotations in special PHP comments ("DocBlocks", "doc-comments"):

  • @after
  • @afterClass
  • @backupGlobals enabled and @backupGlobals disabled
  • @backupStaticAttributes enabled and @backupStaticAttributes disabled
  • @before
  • @beforeClass
  • @codeCoverageIgnore
  • @covers ::functionName
  • @covers ClassName
  • @covers ClassName::methodName (not recommended because too fine-grained)
  • @covers ClassName<extended> (deprecated; will be removed in PHPUnit 10)
  • @covers ClassName::<public> (deprecated; will be removed in PHPUnit 10)
  • @covers ClassName::<protected> (deprecated; will be removed in PHPUnit 10)
  • @covers ClassName::<private> (deprecated; will be removed in PHPUnit 10)
  • @covers ClassName::<!public> (deprecated; will be removed in PHPUnit 10)
  • @covers ClassName::<!protected> (deprecated; will be removed in PHPUnit 10)
  • @covers ClassName::<!private> (deprecated; will be removed in PHPUnit 10)
  • @coversDefaultClass ClassName
  • @coversNothing
  • @dataProvider methodName
  • @dataProvider ClassName::methodName
  • @depends methodName
  • @depends clone methodName
  • @depends shallowClone methodName
  • @depends ClassName::methodName
  • @depends clone ClassName::methodName
  • @depends !clone ClassName::methodName
  • @depends shallowClone ClassName::methodName
  • @depends !shallowClone ClassName::methodName
  • @depends ClassName::class
  • @depends clone ClassName::class
  • @depends !clone ClassName::class
  • @depends shallowClone ClassName::class
  • @depends !shallowClone ClassName::class
  • @doesNotPerformAssertions
  • @group string
  • @small (as alias for @group small)
  • @medium (as alias for @group medium)
  • @large (as alias for @group large)
  • @author string (as alias for @group <string>)
  • @ticket string (as alias for @group <string>)
  • @preCondition
  • @postCondition
  • @preserveGlobalState enabled and @preserveGlobalState disabled
  • @requires PHPUnit <version requirement>
  • @requires PHP <version requirement>
  • @requires extension <extension> <version requirement> (where <version requirement> is optional)
  • @requires function <function> (where <function> is passed to function_exists())
  • @requires function <class>::<method> (where <class> and <method> are passed to method_exists())
  • @requires setting <setting> <value> (where <setting> is a configuration setting that can be queried with ini_set() for comparison with <value>)
  • @requires OS <os> (where <os> is a regular expression that is applied to PHP_OS)
  • @requires OSFAMILY <family> (where <family> is compared to PHP_OS_FAMILY)
  • @runTestsInSeparateProcesses
  • @runInSeparateProcess
  • @test
  • @testdox string
  • @testWith string
  • @uses ::functionName
  • @uses ClassName
  • @uses ClassName::methodName (not recommended because too fine-grained)

PHP 8 introduced attributes as

"a form of structured, syntactic metadata to declarations of classes, properties, functions, methods, parameters and constants. Attributes allow to define configuration directives directly embedded with the declaration of that code."

Support for attributes in PHPUnit will be implemented like so:

The following annotations will not be implemented as attributes:

  • @author string (as alias for @group <string>) because it always only had questionable value
  • @coversDefaultClass ClassName because the workaround it provides is no longer needed as attributes are real syntax

Furthermore, any annotation listed above that is deprecated or not recommended will not be implemented as attributes.

Let us start simple with attributes that do not need parameters:

  • @after will be implemented as PHPUnit\Framework\Attributes\After (only allowed on methods; @after was allowed on classes but did not have an effect there)
  • @afterClass will be implemented as PHPUnit\Framework\Attributes\AfterClass (only allowed on methods; @afterClass was allowed on classes but did not have an effect there)
  • @before will be implemented as PHPUnit\Framework\Attributes\Before (only allowed on methods; @before was allowed on classes but did not have an effect there)
  • @beforeClass will be implemented as PHPUnit\Framework\Attributes\BeforeClass (only allowed on methods; @beforeClass was allowed on classes but did not have an effect there)
  • @codeCoverageIgnore will be implemented as PHPUnit\Framework\Attributes\CodeCoverageIgnore (allowed on classes and methods)
  • @coversNothing will be implemented as PHPUnit\Framework\Attributes\CoversNothing (allowed on classes and methods)
  • @doesNotPerformAssertions will be implemented as PHPUnit\Framework\Attributes\DoesNotPerformAssertions (allowed on classes and methods)
  • @runTestsInSeparateProcesses will be implemented as PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses (only allowed on classes; @runTestsInSeparateProcesses was allowed on methods but did not have an effect there)
  • @runInSeparateProcess will be implemented as PHPUnit\Framework\Attributes\RunInSeparateProcess (only allowed on methods; @runInSeparateProcess was allowed on classes but did not have an effect there)
  • @test will be implemented as PHPUnit\Framework\Attributes\Test (only allowed on methods; @test was allowed on classes but did not have an effect there)
  • @small will be implemented as PHPUnit\Framework\Attributes\Small (only allowed on classes; @small was allowed on methods but it does not make sense to mix tests of different sizes in a test case class)
  • @medium will be implemented as PHPUnit\Framework\Attributes\Medium (only allowed on classes; @medium was allowed on methods but it does not make sense to mix tests of different sizes in a test case class)
  • @large will be implemented as PHPUnit\Framework\Attributes\Large (only allowed on classes; @large was allowed on methods but it does not make sense to mix tests of different sizes in a test case class)
  • @preCondition will be implemented as PHPUnit\Framework\Attributes\PreCondition (only allowed on methods; @preCondition was allowed on classes but did not have an effect there)
  • @postCondition will be implemented as PHPUnit\Framework\Attributes\PostCondition (only allowed on methods; @postCondition was allowed on classes but did not have an effect there)

Here is an example:

<?php declare(strict_types=1);
namespace example;

use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\Small;

#[Small]
final class FooTest extends TestCase
{
    // ...
}

And now for the attributes that need parameters:

  • @backupGlobals enabled will be implemented as PHPUnit\Framework\Attributes\BackupGlobals(true) (allowed on classes and methods)
  • @backupGlobals disabled will be implemented as PHPUnit\Framework\Attributes\BackupGlobals(false) (allowed on classes and methods)
  • @backupStaticAttributes enabled will be implemented as PHPUnit\Framework\Attributes\BackupStaticProperties(true) (allowed on classes and methods)
  • @backupStaticAttributes disabled will be implemented as PHPUnit\Framework\Attributes\BackupStaticProperties(false) (allowed on classes and methods)
  • @covers ClassName will be implemented as PHPUnit\Framework\Attributes\CoversClass(string $className) (only allowed on classes; @covers was allowed on methods but it does not make sense to mix different coverage targets in a test case class)
  • @covers ::functionName will be implemented as PHPUnit\Framework\Attributes\CoversFunction(string $functionName) (only allowed on classes; @covers was allowed on methods but it does not make sense to mix different coverage targets in a test case class)
  • @dataProvider methodName will be implemented as PHPUnit\Framework\Attributes\DataProvider(string $methodName) (only allowed on methods; @dataProvider was allowed on classes but did not have an effect there)
  • @dataProvider ClassName::methodName will be implemented as PHPUnit\Framework\Attributes\DataProviderExternal(string $className, string $methodName) (only allowed on methods; @dataProvider was allowed on classes but did not have an effect there)
  • @depends methodName will be implemented as PHPUnit\Framework\Attributes\Depends(string $methodName) (allowed on methods)
  • @depends clone methodName will be implemented as PHPUnit\Framework\Attributes\DependsUsingDeepClone(string $methodName) (allowed on methods)
  • @depends shallowClone methodName will be implemented as PHPUnit\Framework\Attributes\DependsUsingShallowClone(string $methodName) (allowed on methods)
  • @depends ClassName::methodName will be implemented as PHPUnit\Framework\Attributes\DependsExternal(string $className, string $methodName) (allowed on methods)
  • @depends clone ClassName::methodName will be implemented as PHPUnit\Framework\Attributes\DependsExternalUsingDeepClone(string $className, string $methodName) (allowed on methods)
  • @depends shallowClone ClassName::methodName will be implemented as PHPUnit\Framework\Attributes\DependsExternalUsingShallowClone(string $className, string $methodName) (allowed on methods)
  • @depends ClassName::class will be implemented as PHPUnit\Framework\Attributes\DependsOnClass(string $className) (allowed on methods)
  • @depends clone ClassName::class will be implemented as PHPUnit\Framework\Attributes\DependsOnClassUsingDeepClone(string $className) (allowed on methods)
  • @depends shallowClone ClassName::class will be implemented as PHPUnit\Framework\Attributes\DependsOnClassUsingShallowClone(string $className) (allowed on methods)
  • @group string will be implemented as PHPUnit\Framework\Attributes\Group(string) (allowed on classes and methods)
  • @preserveGlobalState enabled will be implemented as PHPUnit\Framework\Attributes\PreserveGlobalState(true) (allowed on classes and methods)
  • @preserveGlobalState disabled will be implemented as PHPUnit\Framework\Attributes\PreserveGlobalState(false) (allowed on classes and methods)
  • @requires PHP <version requirement> will be implemented as PHPUnit\Framework\Attributes\RequiresPhp(string $versionRequirement) (allowed on classes and methods)
  • @requires extension <extension> <operator> <version requirement> will be implemented as PHPUnit\Framework\Attributes\RequiresPhpExtension(string $extension, ?string $versionRequirement = null) (allowed on classes and methods)
  • @requires function <function> will be implemented as PHPUnit\Framework\Attributes\RequiresFunction(string $function) (allowed on classes and methods)
  • @requires function<class>::<method> will be implemented as PHPUnit\Framework\Attributes\RequiresMethod(string $className, string $methodName) (allowed on classes and methods)
  • @requires PHPUnit <version requirement> will be implemented as PHPUnit\Framework\Attributes\RequiresPhpunit(string $versionRequirement) (allowed on classes and methods)
  • @requires OS <os> will be implemented as PHPUnit\Framework\Attributes\RequiresOperatingSystem(string $regularExpression) (allowed on classes and methods)
  • @requires OSFAMILY <family> will be implemented as PHPUnit\Framework\Attributes\RequiresOperatingSystemFamily(string $operatingSystemFamily) (allowed on classes and methods)
  • @requires setting <setting> <value> will be implemented as PHPUnit\Framework\Attributes\RequiresSetting(string $setting, string $value) (allowed on classes and methods)
  • @testdox string will be implemented as PHPUnit\Framework\Attributes\TestDox(string) (allowed on classes and methods)
  • @testWith string will be implemented as PHPUnit\Framework\Attributes\TestWith(array) (only allowed on methods; @testWith was allowed on classes but did not have an effect there)
  • @ticket string will be implemented as PHPUnit\Framework\Attributes\Ticket(string) (allowed on classes and methods)
  • @uses ClassName will be implemented as PHPUnit\Framework\Attributes\UsesClass(string) (only allowed on classes; @uses was allowed on methods but it does not make sense to mix different coverage targets in a test case class)
  • @uses ::functionName will be implemented as PHPUnit\Framework\Attributes\UsesFunction(string) (only allowed on classes; @uses was allowed on methods but it does not make sense to mix different coverage targets in a test case class)

Here is an example:

<?php declare(strict_types=1);
namespace example;

use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;

#[CoversClass(Foo::class)]
final class FooTest extends TestCase
{
    // ...
}

Metadata

Metadata

Labels

type/enhancementA new idea that should be implemented

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions