PHPUnit:断言两个数组相等,但元素的顺序并不重要

koe*_*oen 121 php phpunit unit-testing assert

当数组中元素的顺序不重要或甚至可能发生变化时,断言两个对象数组相等的好方法是什么?

pry*_*kov 181

assertEquals方法有一个未记录的param $ canonicalize.如果使用$ canonicalize = true,则数组将按PHPUnit数组比较器本身进行排序.

代码示例:

class ArraysTest extends \PHPUnit\Framework\TestCase
{
    public function testEquality()
    {
        $obj1 = $this->getObject(1);
        $obj2 = $this->getObject(2);
        $obj3 = $this->getObject(3);

        $array1 = [$obj1, $obj2, $obj3];
        $array2 = [$obj2, $obj1, $obj3];

        // Pass
        $this->assertEqualsCanonicalizing($array1, $array2);

        // Fail
        $this->assertEquals($array1, $array2);
    }

    private function getObject($value)
    {
        $result = new \stdClass();
        $result->property = $value;
        return $result;
    }
}
Run Code Online (Sandbox Code Playgroud)

在最新版本的PHPUnit上使用比较器源代码:https://github.com/sebastianbergmann/comparator/blob/master/src/ArrayComparator.php#L43

  • @ yi-jiang,这只是解释其他参数意义的最短路径.它更自我描述,然后更干净的变体:`$ this-> assertEquals($ array1,$ array2,"\ $ canonicalize = true",0.0,10,true);`.我可以使用4行而不是1行,但我没有这样做. (11认同)
  • 太棒了.为什么这不是公认的答案,@ koen? (9认同)
  • 您没有指出此解决方案将丢弃密钥. (7认同)
  • 使用`$ delta = 0.0,$ maxDepth = 10,$ canonicalize = true`将参数传递给函数是误导性的 - PHP不支持命名参数.这实际上做的是设置这三个变量,然后立即将它们的值传递给函数.如果已经在本地范围中定义了这三个变量,则会导致问题,因为它们将被覆盖. (6认同)
  • 请注意`$ canonicalize`将被删除:https://github.com/sebastianbergmann/phpunit/issues/3342和`assertEqualsCanonicalizing()`将替换它. (4认同)

小智 35

最简单的方法是使用新的断言方法扩展phpunit.但现在这是一个更简单的方法.未经测试的代码,请验证:

你应用中的某个地方:

 /**
 * Determine if two associative arrays are similar
 *
 * Both arrays must have the same indexes with identical values
 * without respect to key ordering 
 * 
 * @param array $a
 * @param array $b
 * @return bool
 */
function arrays_are_similar($a, $b) {
  // if the indexes don't match, return immediately
  if (count(array_diff_assoc($a, $b))) {
    return false;
  }
  // we know that the indexes, but maybe not values, match.
  // compare the values between the two arrays
  foreach($a as $k => $v) {
    if ($v !== $b[$k]) {
      return false;
    }
  }
  // we have identical indexes, and no unequal values
  return true;
}
Run Code Online (Sandbox Code Playgroud)

在你的测试中:

$this->assertTrue(arrays_are_similar($foo, $bar));
Run Code Online (Sandbox Code Playgroud)


Val*_*spa 34

我的问题是我有2个数组(数组键与我无关,只是值).

例如,我想测试是否

$expected = array("0" => "green", "2" => "red", "5" => "blue", "9" => "pink");
Run Code Online (Sandbox Code Playgroud)

有相同的内容(与我无关的顺序)

$actual = array("0" => "pink", "1" => "green", "3" => "yellow", "red", "blue");
Run Code Online (Sandbox Code Playgroud)

所以我使用了array_diff.

最终结果是(如果数组相等,差异将导致空数组).请注意,差异是双向计算的(谢谢@beret,@ GordonM)

$this->assertEmpty(array_merge(array_diff($expected, $actual), array_diff($actual, $expected)));
Run Code Online (Sandbox Code Playgroud)

有关更详细的错误消息(在调试时),您也可以像这样测试(感谢@DenilsonSá):

$this->assertSame(array_diff($expected, $actual), array_diff($actual, $expected));
Run Code Online (Sandbox Code Playgroud)

里面有bug的旧版本:

$ this-> assertEmpty(array_diff($ array2,$ array1));

  • 你应该两种方式执行array_diff或array_diff_assoc.如果一个数组是另一个数组的超集,那么在一个方向上的array_diff将为空,但在另一个方向上为非空.`$ a1 = [1,2,3,4,5]; $ a2 = [1,3,5]; var_dump(array_diff($ a1,$ a2)); var_dump(array_diff($ a2,$ a1))` (3认同)
  • 如果assertEmpty不为空,则不会打印该数组,这在调试测试时很不方便。我建议使用:`$ this-> assertSame(array_diff($ expected,$ actual),array_diff($ actual,$ expected),$ message);`,因为这将以最小的输出最有用的错误消息。额外的代码。这是因为* A \ B = B \ A⇔A \ B和B \ A为空⇔A = B * (2认同)

rod*_*ira 20

另一种可能性:

  1. 对两个数组排序
  2. 将它们转换为字符串
  3. 断言两个字符串是相等的

$arr = array(23, 42, 108);
$exp = array(42, 23, 108);

sort($arr);
sort($exp);

$this->assertEquals(json_encode($exp), json_encode($arr));
Run Code Online (Sandbox Code Playgroud)


ksi*_*mka 14

简单的帮助方法

protected function assertEqualsArrays($expected, $actual, $message) {
    $this->assertTrue(count($expected) == count(array_intersect($expected, $actual)), $message);
}
Run Code Online (Sandbox Code Playgroud)

或者,如果在数组不相等时需要更多调试信息

protected function assertEqualsArrays($expected, $actual, $message) {
    sort($expected);
    sort($actual);

    $this->assertEquals($expected, $actual, $message);
}
Run Code Online (Sandbox Code Playgroud)


Rod*_*zel 8

如果数组是可排序的,我会在检查相等性之前对它们进行排序.如果没有,我会将它们转换为某种类型的集合并进行比较.


小智 7

即使您不关心顺序,考虑到这一点可能会更容易:

尝试:

asort($foo);
asort($bar);
$this->assertEquals($foo, $bar);
Run Code Online (Sandbox Code Playgroud)


cal*_*ari 6

使用array_diff():

$a1 = array(1, 2, 3);
$a2 = array(3, 2, 1);

// error when arrays don't have the same elements (order doesn't matter):
$this->assertEquals(0, count(array_diff($a1, $a2)) + count(array_diff($a2, $a1)));
Run Code Online (Sandbox Code Playgroud)

或者有2个断言(更容易阅读):

// error when arrays don't have the same elements (order doesn't matter):
$this->assertEquals(0, count(array_diff($a1, $a2)));
$this->assertEquals(0, count(array_diff($a2, $a1)));
Run Code Online (Sandbox Code Playgroud)


moi*_*s52 6

给定的解决方案并不适合我,因为我希望能够处理多维数组并清楚地了解两个数组之间的差异。

这是我的功能

public function assertArrayEquals($array1, $array2, $rootPath = array())
{
    foreach ($array1 as $key => $value)
    {
        $this->assertArrayHasKey($key, $array2);

        if (isset($array2[$key]))
        {
            $keyPath = $rootPath;
            $keyPath[] = $key;

            if (is_array($value))
            {
                $this->assertArrayEquals($value, $array2[$key], $keyPath);
            }
            else
            {
                $this->assertEquals($value, $array2[$key], "Failed asserting that `".$array2[$key]."` matches expected `$value` for path `".implode(" > ", $keyPath)."`.");
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后使用它

$this->assertArrayEquals($array1, $array2, array("/"));
Run Code Online (Sandbox Code Playgroud)


t.h*_*ntz 5

我们在测试中使用以下包装器方法:

/**
 * Assert that two arrays are equal. This helper method will sort the two arrays before comparing them if
 * necessary. This only works for one-dimensional arrays, if you need multi-dimension support, you will
 * have to iterate through the dimensions yourself.
 * @param array $expected the expected array
 * @param array $actual the actual array
 * @param bool $regard_order whether or not array elements may appear in any order, default is false
 * @param bool $check_keys whether or not to check the keys in an associative array
 */
protected function assertArraysEqual(array $expected, array $actual, $regard_order = false, $check_keys = true) {
    // check length first
    $this->assertEquals(count($expected), count($actual), 'Failed to assert that two arrays have the same length.');

    // sort arrays if order is irrelevant
    if (!$regard_order) {
        if ($check_keys) {
            $this->assertTrue(ksort($expected), 'Failed to sort array.');
            $this->assertTrue(ksort($actual), 'Failed to sort array.');
        } else {
            $this->assertTrue(sort($expected), 'Failed to sort array.');
            $this->assertTrue(sort($actual), 'Failed to sort array.');
        }
    }

    $this->assertEquals($expected, $actual);
}
Run Code Online (Sandbox Code Playgroud)


小智 5

如果键是相同但不按顺序,这应该解决它.

您只需按相同顺序获取密钥并比较结果即可.

 /**
 * Assert Array structures are the same
 *
 * @param array       $expected Expected Array
 * @param array       $actual   Actual Array
 * @param string|null $msg      Message to output on failure
 *
 * @return bool
 */
public function assertArrayStructure($expected, $actual, $msg = '') {
    ksort($expected);
    ksort($actual);
    $this->assertSame($expected, $actual, $msg);
}
Run Code Online (Sandbox Code Playgroud)