Каков хороший способ утверждать, что два массива объектов равны, когда порядок элементов в массиве неважен или даже может быть изменен?
PHPUnit: утверждают, что два массива равны, но порядок элементов не важен
Ответ 1
Самый чистый способ сделать это - расширить 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));
Ответ 2
Метод assertEquals имеет недокументированный параметр $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", $delta = 0.0, $maxDepth = 10, $canonicalize = 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
Ответ 3
Моя проблема заключалась в том, что у меня было 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));
Ответ 4
Еще одна возможность:
- Сортировка обоих массивов
- Преобразовать их в строку
- Утверждение, что обе строки равны
$arr = array(23, 42, 108);
$exp = array(42, 23, 108);
sort($arr);
sort($exp);
$this->assertEquals(json_encode($exp), json_encode($arr));
Ответ 5
Простой вспомогательный метод
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);
}
Ответ 6
Если массив отсортирован, я бы сортировал их перед проверкой равенства. Если нет, я бы преобразовал их в какие-то типы и сравнил их.
Ответ 7
В наших тестах используется следующий метод обертки:
/**
* 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);
}
Ответ 8
Если ключи одинаковы, но не в порядке, это должно решить.
Вам просто нужно получить ключи в том же порядке и сравнить результаты.
/**
* 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);
}
Ответ 9
Использование 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)));
Или с двумя утверждениями (легче читать):
// 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)));
Ответ 10
Данные решения не помогли мне, потому что я хотел иметь возможность обрабатывать многомерный массив и иметь четкое сообщение о том, что отличается между двумя массивами.
Вот моя функция
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("/"));
Ответ 11
Несмотря на то, что вы не заботитесь о заказе, это может быть проще принять во внимание:
Try:
asort($foo);
asort($bar);
$this->assertEquals($foo, $bar);
Ответ 12
Я написал простой код, чтобы сначала получить все ключи из многомерного массива:
/**
* 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));
НТН
Ответ 13
Если вы хотите проверить только те значения массива, которые вы можете сделать:
$this->assertEquals(array_values($arrayOne), array_values($arrayTwo));
Ответ 14
Другой вариант, как будто у вас еще недостаточно, состоит в объединении assertArraySubset
в сочетании с assertCount
, чтобы сделать ваше утверждение. Таким образом, ваш код будет выглядеть примерно так.
self::assertCount(EXPECTED_NUM_ELEMENT, $array);
self::assertArraySubset(SUBSET, $array);
Таким образом, вы не зависимы от порядка, но все же утверждаете, что все ваши элементы присутствуют.