-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Support PHP 8 attributes for adding metadata to test classes and test methods as well as tested code units #4502
Description
PHPUnit currently supports the following annotations in special PHP comments ("DocBlocks", "doc-comments"):
@after@afterClass@backupGlobals enabledand@backupGlobals disabled@backupStaticAttributes enabledand@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 enabledand@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 tofunction_exists())@requires function <class>::<method>(where<class>and<method>are passed tomethod_exists())@requires setting <setting> <value>(where<setting>is a configuration setting that can be queried withini_set()for comparison with<value>)@requires OS <os>(where<os>is a regular expression that is applied toPHP_OS)@requires OSFAMILY <family>(where<family>is compared toPHP_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:
- PHPUnit 10 will first look for metadata in attributes before it looks at comments
- When metadata is found in attributes, metadata in comments is ignored
- Support for metadata in comments is closed for further development (bugs will be fixed, but no new functionality will be implemented based on annotations)
- Support for metadata in comments will be deprecated in PHPUnit 11
- Support for metadata in comments will be removed in PHPUnit 12
The following annotations will not be implemented as attributes:
@author string(as alias for@group <string>) because it always only had questionable value@coversDefaultClass ClassNamebecause 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:
@afterwill be implemented asPHPUnit\Framework\Attributes\After(only allowed on methods;@afterwas allowed on classes but did not have an effect there)@afterClasswill be implemented asPHPUnit\Framework\Attributes\AfterClass(only allowed on methods;@afterClasswas allowed on classes but did not have an effect there)@beforewill be implemented asPHPUnit\Framework\Attributes\Before(only allowed on methods;@beforewas allowed on classes but did not have an effect there)@beforeClasswill be implemented asPHPUnit\Framework\Attributes\BeforeClass(only allowed on methods;@beforeClasswas allowed on classes but did not have an effect there)@codeCoverageIgnorewill be implemented asPHPUnit\Framework\Attributes\CodeCoverageIgnore(allowed on classes and methods)@coversNothingwill be implemented asPHPUnit\Framework\Attributes\CoversNothing(allowed on classes and methods)@doesNotPerformAssertionswill be implemented asPHPUnit\Framework\Attributes\DoesNotPerformAssertions(allowed on classes and methods)@runTestsInSeparateProcesseswill be implemented asPHPUnit\Framework\Attributes\RunTestsInSeparateProcesses(only allowed on classes;@runTestsInSeparateProcesseswas allowed on methods but did not have an effect there)@runInSeparateProcesswill be implemented asPHPUnit\Framework\Attributes\RunInSeparateProcess(only allowed on methods;@runInSeparateProcesswas allowed on classes but did not have an effect there)@testwill be implemented asPHPUnit\Framework\Attributes\Test(only allowed on methods;@testwas allowed on classes but did not have an effect there)@smallwill be implemented asPHPUnit\Framework\Attributes\Small(only allowed on classes;@smallwas allowed on methods but it does not make sense to mix tests of different sizes in a test case class)@mediumwill be implemented asPHPUnit\Framework\Attributes\Medium(only allowed on classes;@mediumwas allowed on methods but it does not make sense to mix tests of different sizes in a test case class)@largewill be implemented asPHPUnit\Framework\Attributes\Large(only allowed on classes;@largewas allowed on methods but it does not make sense to mix tests of different sizes in a test case class)@preConditionwill be implemented asPHPUnit\Framework\Attributes\PreCondition(only allowed on methods;@preConditionwas allowed on classes but did not have an effect there)@postConditionwill be implemented asPHPUnit\Framework\Attributes\PostCondition(only allowed on methods;@postConditionwas 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 enabledwill be implemented asPHPUnit\Framework\Attributes\BackupGlobals(true)(allowed on classes and methods)@backupGlobals disabledwill be implemented asPHPUnit\Framework\Attributes\BackupGlobals(false)(allowed on classes and methods)@backupStaticAttributes enabledwill be implemented asPHPUnit\Framework\Attributes\BackupStaticProperties(true)(allowed on classes and methods)@backupStaticAttributes disabledwill be implemented asPHPUnit\Framework\Attributes\BackupStaticProperties(false)(allowed on classes and methods)@covers ClassNamewill be implemented asPHPUnit\Framework\Attributes\CoversClass(string $className)(only allowed on classes;@coverswas allowed on methods but it does not make sense to mix different coverage targets in a test case class)@covers ::functionNamewill be implemented asPHPUnit\Framework\Attributes\CoversFunction(string $functionName)(only allowed on classes;@coverswas allowed on methods but it does not make sense to mix different coverage targets in a test case class)@dataProvider methodNamewill be implemented asPHPUnit\Framework\Attributes\DataProvider(string $methodName)(only allowed on methods;@dataProviderwas allowed on classes but did not have an effect there)@dataProvider ClassName::methodNamewill be implemented asPHPUnit\Framework\Attributes\DataProviderExternal(string $className, string $methodName)(only allowed on methods;@dataProviderwas allowed on classes but did not have an effect there)@depends methodNamewill be implemented asPHPUnit\Framework\Attributes\Depends(string $methodName)(allowed on methods)@depends clone methodNamewill be implemented asPHPUnit\Framework\Attributes\DependsUsingDeepClone(string $methodName)(allowed on methods)@depends shallowClone methodNamewill be implemented asPHPUnit\Framework\Attributes\DependsUsingShallowClone(string $methodName)(allowed on methods)@depends ClassName::methodNamewill be implemented asPHPUnit\Framework\Attributes\DependsExternal(string $className, string $methodName)(allowed on methods)@depends clone ClassName::methodNamewill be implemented asPHPUnit\Framework\Attributes\DependsExternalUsingDeepClone(string $className, string $methodName)(allowed on methods)@depends shallowClone ClassName::methodNamewill be implemented asPHPUnit\Framework\Attributes\DependsExternalUsingShallowClone(string $className, string $methodName)(allowed on methods)@depends ClassName::classwill be implemented asPHPUnit\Framework\Attributes\DependsOnClass(string $className)(allowed on methods)@depends clone ClassName::classwill be implemented asPHPUnit\Framework\Attributes\DependsOnClassUsingDeepClone(string $className)(allowed on methods)@depends shallowClone ClassName::classwill be implemented asPHPUnit\Framework\Attributes\DependsOnClassUsingShallowClone(string $className)(allowed on methods)@group stringwill be implemented asPHPUnit\Framework\Attributes\Group(string)(allowed on classes and methods)@preserveGlobalState enabledwill be implemented asPHPUnit\Framework\Attributes\PreserveGlobalState(true)(allowed on classes and methods)@preserveGlobalState disabledwill be implemented asPHPUnit\Framework\Attributes\PreserveGlobalState(false)(allowed on classes and methods)@requires PHP <version requirement>will be implemented asPHPUnit\Framework\Attributes\RequiresPhp(string $versionRequirement)(allowed on classes and methods)@requires extension <extension> <operator> <version requirement>will be implemented asPHPUnit\Framework\Attributes\RequiresPhpExtension(string $extension, ?string $versionRequirement = null)(allowed on classes and methods)@requires function <function>will be implemented asPHPUnit\Framework\Attributes\RequiresFunction(string $function)(allowed on classes and methods)@requires function<class>::<method>will be implemented asPHPUnit\Framework\Attributes\RequiresMethod(string $className, string $methodName)(allowed on classes and methods)@requires PHPUnit <version requirement>will be implemented asPHPUnit\Framework\Attributes\RequiresPhpunit(string $versionRequirement)(allowed on classes and methods)@requires OS <os>will be implemented asPHPUnit\Framework\Attributes\RequiresOperatingSystem(string $regularExpression)(allowed on classes and methods)@requires OSFAMILY <family>will be implemented asPHPUnit\Framework\Attributes\RequiresOperatingSystemFamily(string $operatingSystemFamily)(allowed on classes and methods)@requires setting <setting> <value>will be implemented asPHPUnit\Framework\Attributes\RequiresSetting(string $setting, string $value)(allowed on classes and methods)@testdox stringwill be implemented asPHPUnit\Framework\Attributes\TestDox(string)(allowed on classes and methods)@testWith stringwill be implemented asPHPUnit\Framework\Attributes\TestWith(array)(only allowed on methods;@testWithwas allowed on classes but did not have an effect there)@ticket stringwill be implemented asPHPUnit\Framework\Attributes\Ticket(string)(allowed on classes and methods)@uses ClassNamewill be implemented asPHPUnit\Framework\Attributes\UsesClass(string)(only allowed on classes;@useswas allowed on methods but it does not make sense to mix different coverage targets in a test case class)@uses ::functionNamewill be implemented asPHPUnit\Framework\Attributes\UsesFunction(string)(only allowed on classes;@useswas 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
{
// ...
}