如何在PHPUnit测试用例中分组、复用与混合自定义断言类?
PHPUnit自定义断言的多类混合使用问题
现有实现方式
我目前通过继承PHPUnit默认的TestCase来编写自定义断言,分为两类:
1. 通用型断言
这类断言和业务领域无关,比如检查数组是否仅包含特定值的断言,实现如下:
<?php namespace Kopernikus\TimrReportManager; use PHPUnit\Framework\TestCase; abstract class ArrayContainsValueTestCase extends TestCase { public static function assertArrayOnlyContainsTrue(array $haystack): void { static::assertArrayOnlyContainsSameValue(true, $haystack); } public static function assertArrayOnlyContainsSameValue(mixed $expectedValue, array $haystack): void { $haystack = array_unique($haystack); static::assertTrue(static::areOnlySameValuesInArray($expectedValue, $haystack), message: 'The array contains of different values, yet sameness was expected'); } private static function areOnlySameValuesInArray(mixed $expectedValue, array $haystack): bool { $haystack = array_unique($haystack); if (count($haystack) !== 1) { return false; } return reset($haystack) === $expectedValue; } public static function assertArrayOnlyContainsFalse(array $haystack) { static::assertArrayOnlyContainsSameValue(false, $haystack); } }
2. 业务绑定型断言
这类断言和项目业务强关联,比如检查自定义TimeEntry值对象是否包含唯一指定工单ID的断言,独立成类实现:
<?php namespace Kopernikus\TimrReportManager; use Kopernikus\TimrReportManager\Dto\TimeEntry; use PHPUnit\Framework\TestCase; abstract class TimeEntryTestCase extends TestCase { public static function assertTimeEntryHasTicketId(TimeEntry $timeEntry, int $ticketNumber) { $ticketNumberHashtag = '#' . (string)$ticketNumber; $count = substr_count($timeEntry->description, '#'); static::assertSame(1, $count, 'the time entry must only contain one hashtag for the ticket id'); static::assertSame($timeEntry->ticket, $ticketNumberHashtag); } }
现有方案的痛点
当测试类MyTestThatRequiredBothAssertions需要同时使用这两类断言时,若通过继承链实现:
ArrayContainsValueTestCase extends TestCaseTimeEntryTestCase extends ArrayContainsValueTestCaseMyTestThatRequiredBothAssertions extends TimeEntryTestCase
这种方式存在明显问题:
- 并非所有
TimeEntryTestCase的子类都需要ArrayContainsValueTestCase的断言,强制继承会造成不必要的依赖 - 后续新增更多自定义断言类时,继承树会越来越复杂混乱
我原本希望让两类断言类都直接继承TestCase,然后在测试类中同时使用,类似:
class MyTestThatRequiredBothAssertions extends ArrayContainsValueTestCase, TimeEntryTestCase
但PHP仅支持单继承,这种写法不可行。
需求与疑问
我需要一种能关注点分离的解决方案,满足以下要求:
- 自定义断言拆分到多个独立文件中
- 测试类可以自由混合使用不同的断言类,仅在需要时建立依赖
- 保留IDE对断言方法的自动补全支持
我曾考虑过使用Trait,但Trait无法继承类,trait MyCustomAssertion extends TestCase这种写法不被允许。请问还有其他可行的解决方案吗?
内容的提问来源于stack exchange,提问作者k0pernikus
相关产品推荐
相关产品推荐

