PHPUnit: assert два массива равны, но порядок элементов не важен
как можно утверждать, что два массива объектов равны, когда порядок элементов в массиве неважен или даже может быть изменен?
15 ответов:
самый чистый способ сделать это-расширить 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));
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->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#L43
моя проблема была в том, что у меня было 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));
еще варианты:
- Сортировать оба массива
- преобразовать их в строку
- утверждать, что обе строки равны
$arr = array(23, 42, 108); $exp = array(42, 23, 108); sort($arr); sort($exp); $this->assertEquals(json_encode($exp), json_encode($arr));
простой вспомогательный метод
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); }
Если массив сортируется, я бы отсортировал их обоих перед проверкой равенства. Если нет, я бы преобразовал их в какие-то наборы и сравнил их.
мы используем следующий метод обертки в наших тестах:
/** * 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); }
используя 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)));
если ключи одинаковы, но не в порядке, это должно решить эту проблему.
вы просто должны получить ключи в том же порядке и сравните результаты.
/** * 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); }
даже если вы не заботитесь о порядке, было бы проще принять это во внимание:
попробуй:
asort($foo); asort($bar); $this->assertEquals($foo, $bar);
данного решения не работа для меня, потому что я хотел быть в состоянии обрабатывать многомерный массив и иметь четкое сообщение о том, что отличается между двумя массивами.
вот моя функция
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("/"));
Я написал простой код, чтобы сначала получить все ключи из многомерного массива:
/** * 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));
HTH
Если значения-это только int или строки, а не массивы нескольких уровней....
почему бы просто не отсортировать массивы, преобразовать их в строку...
$mapping = implode(',', array_sort($myArray)); $list = implode(',', array_sort($myExpectedArray));
... а затем сравнить строку:
$this->assertEquals($myExpectedArray, $myArray);
Если вы хотите проверить только значения массива, вы можете сделать:
$this->assertEquals(array_values($arrayOne), array_values($arrayTwo));
другой вариант, как будто у вас еще не было достаточно, чтобы объединить
assertArraySubset
в сочетании сassertCount
чтобы сделать свое утверждение. Итак, ваш код будет выглядеть примерно так.
self::assertCount(EXPECTED_NUM_ELEMENT, $array); self::assertArraySubset(SUBSET, $array);
таким образом, вы не зависите от порядка, но все же утверждаете, что все ваши элементы присутствуют.