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

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

132949 次浏览

如果数组是可排序的,那么在检查相等性之前,我会对它们进行排序。如果没有,我会把它们转换成某种集合,然后比较它们。

最简单的方法是使用一个新的断言方法来扩展 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;
}

在你的测试中:

$this->assertTrue(arrays_are_similar($foo, $bar));

简单的辅助方法

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

或者当数组不相等时需要更多的调试信息

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


$this->assertEquals($expected, $actual, $message);
}

我的问题是我有2个数组(数组键对我来说不相关,只有值)。

例如,我想测试

$expected = array("0" => "green", "2" => "red", "5" => "blue", "9" => "pink");

内容(与我无关的订单)与

$actual = array("0" => "pink", "1" => "green", "3" => "yellow", "red", "blue");

所以我用了 Array _ diff

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

$this->assertEmpty(array_merge(array_diff($expected, $actual), array_diff($actual, $expected)));

要获得更详细的错误消息(在调试时) ,还可以这样进行测试(多谢@DenilsonS á) :

$this->assertSame(array_diff($expected, $actual), array_diff($actual, $expected));

老版本里面有虫子:

$this-> assertEmpty (array _ diff ($array2,$array1)) ;

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

/**
* 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);
}

还有一种可能性:

  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));

如果钥匙是一样的,但是没有顺序,这应该可以解决问题。

你只需要按同样的顺序拿到钥匙,然后比较结果。

 /**
* 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);
}

您可以使用在 PHPUnit 7.5中添加的 规范化方法。如果使用此方法比较数组,则这些数组将按 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;
}
}

在旧版本的 PHPUnit 中,您可以使用一个未记录的参数 $canonicalize of AssertEquals方法。如果你通过 $canonicalize = true,你会得到同样的效果:

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->assertEquals($array1, $array2, "\$canonicalize = true", 0.0, 10, true);


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


private function getObject($value)
{
$result = new stdclass();
$result->property = $value;
return $result;
}
}

PHPUnit 最新版本的数组比较器源代码: https://github.com/sebastianbergmann/comparator/blob/master/src/ArrayComparator.php#L46

使用 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)));

或者使用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)));

我编写了一些简单的代码,首先从一个多维数组中获取所有键:

 /**
* Returns all keys from arrays with any number of levels
* @param  array
* @return array
*/
protected function getAllArrayKeys($array)
{
$keys = array();
foreach ($array as $key => $element) {
$keys[] = $key;
if (is_array($array[$key])) {
$keys = array_merge($keys, $this->getAllArrayKeys($array[$key]));
}
}
return $keys;
}

然后测试它们的结构是否相同,而不管键的顺序如何:

    $expectedKeys = $this->getAllArrayKeys($expectedData);
$actualKeys = $this->getAllArrayKeys($actualData);
$this->assertEmpty(array_diff($expectedKeys, $actualKeys));

高温

给定的解决方案没有为我做这项工作,因为我想能够处理多维数组,并有一个明确的消息,什么是不同的两个数组。

这是我的功能

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)."`.");
}
}
}
}

那就好好利用

$this->assertArrayEquals($array1, $array2, array("/"));

即使你不在乎订单,也可能更容易考虑到这一点:

试试:

asort($foo);
asort($bar);
$this->assertEquals($foo, $bar);

另一种选择是将 assertArraySubsetassertCount结合起来做出断言,如果您还没有做到这一点的话。所以,你的代码看起来像。

Self: : assertCount (EXPECTED _ NUM _ ELEMENT,$array) ; Self: : assertArraySubset (SUBSET,$array) ;

这样,您可以独立于订单,但是仍然可以断言所有元素都存在。

如果只想测试数组的值,可以这样做:

$this->assertEquals(array_values($arrayOne), array_values($arrayTwo));

如果值只是 int 或字符串,并且没有多级数组... ..。

为什么不把数组排序,转换成字符串..。

    $mapping = implode(',', array_sort($myArray));


$list = implode(',', array_sort($myExpectedArray));

然后比较字符串:

    $this->assertEquals($myExpectedArray, $myArray);