首页 > 解决方案 > 如何使用数据提供程序测试一个键在多维数组的不同级别不存在?

问题描述

我有一种创建大型多维数组的方法。我正在尝试对此方法运行一系列单元测试。我正在尝试进行正面测试(测试某些数组键是否设置)和负面测试(测试某些数组键不存在)。问题是设置对象需要大量代码,并且此方法接受我要测试的许多不同参数。由于这些原因,我想使用数据提供者对该方法运行一系列测试。这样我就可以设置一次对象并使用数据提供程序来获取数组参数和预期的数组值。

我可以通过$this->assertArraySubset()在数据提供者中调用并包含预期的数组结构来进行正面测试。但是我想不出一个好方法来测试某些数组键不存在(我的否定测试),因为这些数组键位于数组的不同级别。

这是我的代码示例,因此您可以看到我正在处理的内容:

<?php

class MyClassTest {

    public function providerForFunctionThatCreatesArray() {
        return [
            [
                '{foo:bar}', # some data returned by service A
                '{foo:baz}', # some data returned by service B
                'c' # checking that this key does not exist in the array
            ],
            [
                '{foo:barbaz}',
                '{foo:bazbar}',
                'd' # I also want to check that this key does not exist but in a different level of the array (i.e. $array['b'])
            ],

        ]
    }

    /**
     * @dataProvider providerForFunctionThatCreatesArray
     */
    public function testFunctionThatCreatesArray($dataFromServiceA, $dataFromServiceB, $expectedKeyNotExists) {

        $serviceA = $this
            ->getMockBuilder(ServiceA::class)
            ->setMethods(['get_data'])
            ->getMock();
        $serviceA->expects($this->any())->method('get_data')->willReturnValue($dataFromServiceA);

        $serviceB = $this
            ->getMockBuilder(ServiceB::class)
            ->setMethods(['get_data'])
            ->getMock();
        $serviceB->expects($this->any())->method('get_data')->willReturnValue($dataFromServiceB);
        $myClass = new MyClass($serviceA, $serviceB);

        $array = $myClass->functionThatCreatesArray();

        // This is the function that checks that keys do not exist in the array
        $this->assertArrayNotHasKey($expectedKeyNotExists, $array['a']);
    }
}

这些{foo:...}东西是我的函数使用的某些服务返回的数据。不同的值会影响我的函数创建的数组。我为这些服务创建了模拟,并使用数据提供者来强制服务返回的值。

如您所见,我的数据提供程序还返回一个键作为我的测试函数 ( $expectedKeyNotExists) 的第三个参数。这是我正在检查的键在我的数组中不存在。但是,d关键是我想在数组的不同部分检查一个,例如$array['b']代替$array['a']. 如果我运行上述测试,它将检查 'd' 不存在$array['a'],这不是我想要的。什么是构建我的测试以动态检查我的键不存在于数组的不同部分的好方法?

我想过让我的数据提供者返回第四个键,这是要使用的父键。像这样:

return [
    [
        '{foo:bar}', # some data returned by service A
        '{foo:baz}', # some data returned by service B
        'c', # checking that this key does not exist in the array   
        'a' # parent key
    ],
    [
        '{foo:barbaz}', # some data returned by service A
        '{foo:bazbar}', # some data returned by service B
        'd', # checking that this key does not exist in the array   
        'b' # parent key
    ]
]

然后我可以像这样进行测试:

public function testFunctionThatCreatesArray($dataFromServiceA, $dataFromServiceB, $expectedKeyNotExists, $parentKey) {
    // ... snip ...
    $this->assertArrayNotHasKey($expectedKeyNotExists, $array[$parentKey]);
}

上述方法的问题在于,在检查数组不同级别的键的情况下,它不是很灵活。例如,如果我想检查在$array['a']['e']['f']and处不存在的键怎么办$array['a']['g']['h']

标签: phpunit-testingphpunit

解决方案


据我所知,Phpunit 不会递归地为数组键提供断言。

您可以使用自己的断言扩展 Phpunit,但我会轻而易举地开始并向测试用例添加一个私有辅助方法,该方法返回一个布尔值,无论该数组是否递归地作为键(检查现有的问答材料,例如Search for a key in an数组,递归和其他关于如何递归检查数组的键),然后对 false 进行断言,例如:

$this->assertFalse(
    $this->arrayHasKeyRecursive($array, $expected), 
    "key must not exist"
);

请记住,当您编写代码以支持您的测试时,让它变得非常愚蠢(有时您还需要将帮助程序置于测试之下,这样您的测试就不会因为错误而欺骗您)。


交叉引用


推荐阅读