【问题标题】:PHPUnit: How to test all implementations of a contract?PHPUnit:如何测试合约的所有实现?
【发布时间】:2019-12-17 11:53:35
【问题描述】:

假设我们有一个由接口或抽象实现(例如,ISortable)和期望(例如,ISortable::sort() 实际上以不区分大小写的方式对项目进行排序)组成的合约。

ISortable 可以有任意数量的具体实现。我们如何编写一个套件,为Isortable 的所有实现强制执行Isortable 契约,这样我们就可以只实现要检查的具体类的列表或生成所述列表的反射机制。

所以基本上我们想要一个测试套件,它在一个类列表上迭代运行,每个类作为实例化或静态测试的参数。

【问题讨论】:

    标签: unit-testing automated-tests phpunit


    【解决方案1】:

    您可以对此类变体使用@dataProvider 注释。在下面找到一个示例,您可以在其中指定类列表,并且将为所有人运行相同的测试。

    class ISortableImplementationsTest extends TestCase
    {
        /**
         * @dataProvider getImplementationClass
         */
        public function testSort(string $implClass): void
        {
            //First make sure if the class actually implements the interface. 
            //This assertion is not required if you added the list of classes manually
            $this->assertTrue(
                in_array(ISortable::class, class_implements($implClass)),
                sprintf(
                    "Test Failed. The class %s does not implement the expected interface %s.",
                    $implClass,
                    ISortable::class
                )
            );
    
            // Now the real test of implementation begins here
            $unsorted = ['s', 'a', 'd'];
    
            $subjectClassObject = new $implClass($unsorted);
    
            $result = $subjectClassObject->sort();
    
            $this->assertEquals(
                ['a', 'd', 's'],
                $result,
                sprintf(
                    "Test for class %s failed. Given %s. Expected %s. Got %s.",
                    $implClass,
                    print_r($unsorted, true),
                    print_r(['a', 'd', 's'], true),
                    print_r($result, true)
                )
            );
        }
    
        public function getImplementationClass() : array
        {
            return [
                [ ISortableImplementaionClass1::class ],
                [ ISortableImplementaionClass2::class ],
            ];
        }
    }
    

    更进一步,您可能可以使用these 方法之一来自动加载整个类列表。以上代码在 PHP 7.4 上测试。

    但是,我不建议对 Clean Code 进行上述测试。通常,每个单元测试都指向代码中的一个类。它的好处是您可以随时返回并进行单元测试并相应地进行更改。我知道仅测试行为很好,但有时您也只想在特殊情况下测试实现。

    我更喜欢使用模板方法模式进行单元测试,基类包含所有常见的内容,以及提供所有特定内容的扩展。

    abstract class ISortableTestTemplate extends TestCase
    {
        abstract protected function getISortableInstance(array $unsorted): ISortable;
    
        public function testSort(): void
        {
            $unsorted = ['s', 'a', 'd'];
    
            $subjectClassObject = $this->getISortableInstance($unsorted);
    
            $result = $subjectClassObject->sort();
    
            $this->assertEquals(
                ['a', 'd', 's'],
                $result,
                sprintf(
                    "Test failed. Given %s. Expected %s. Got %s.",
                    print_r($unsorted, true),
                    print_r(['a', 'd', 's'], true),
                    print_r($result, true)
                )
            );
        }
    }
    
    
    class ISortableImplementationClass1Test extends ISortableTestTemplate
    {
        protected function getISortableInstance(array $unsorted): ISortable
        {
            return new ISortableImplementaionClass1($unsorted);
        }
    
        public function testSort(): void
        {
            parent::testSort();
        }
    }
    

    【讨论】:

    • 谢谢!我一直在 DI 上下文中寻找这种模式,其中实现对于合同的设计并由不同的人完成:以便消费者(以及接口)的作者可以编写一些需要通过任何人的测试执行;那么实现的作者可以为实现添加更具体的测试。
    • 第一种方法的优点是它是被动的:只要可以自动检测到依赖类(并且可以),它就可以在依赖项开发人员没有任何操作的情况下工作。第二个需要依赖开发者明确的行动来添加测试实现。
    猜你喜欢
    • 2020-04-28
    • 2010-11-27
    • 1970-01-01
    • 2015-03-20
    • 1970-01-01
    • 2023-03-09
    • 2018-04-27
    • 2014-08-21
    • 2016-05-24
    相关资源
    最近更新 更多