PHPUnit: стверджують, що два масиви рівні, але порядок елементів не важливий


132

Який хороший спосіб стверджувати, що два масиви об’єктів рівні, коли порядок елементів у масиві є неважливим або навіть може змінюватися?


Чи турбуєтесь ви про об'єкти в масиві, що дорівнює або просто те, що в обох масивах є x кількість об'єктів y?
edorian

@edorian Обидва були б найцікавішими. У моєму випадку, хоча у кожному масиві є лише один об'єкт y.
koen

будь ласка, визначте рівним . Чи порівнюєте відсортовані хеши об'єктів, що вам потрібно? Напевно, вам доведеться все одно сортувати об’єкти .
приймає

@takeshin Рівно як у ==. У моєму випадку вони є об'єктами цінності, тому однаковість не потрібна. Можливо, я міг би створити власний метод затвердження. Що мені знадобиться в цьому, це порахувати кількість елементів у кожному масиві, і для кожного елемента в обох на рівних (==) повинно існувати.
koen

7
Власне, на PHPUnit 3.7.24 $ this-> assertEquals стверджує, що масив містить ті самі ключі та значення, не враховуючи в якому порядку.
Дерексон

Відповіді:


38

Найчистішим способом зробити це було б поширити 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));

Крейг, ти близький тому, що я спробував спочатку. Насправді array_diff - це те, що мені потрібно, але, здається, це не працює для об’єктів. Я написав своє власне твердження, як пояснено тут: phpunit.de/manual/current/en/extending-phpunit.html
koen

Належне посилання зараз із https та без www: phpunit.de/manual/current/en/extending-phpunit.html
Xavi Montero

частина foreach непотрібна - array_diff_assoc вже порівнює і ключі, і значення. EDIT: і вам також потрібно перевірити count(array_diff_assoc($b, $a)).
JohnSmith

212

Можна використовувати метод assertEqualsCanonicalizing, який був доданий у 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 методу 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


10
Фантастичний. Чому це не прийнята відповідь, @koen?
rinogo

7
Використання $delta = 0.0, $maxDepth = 10, $canonicalize = trueдля передачі параметрів у функцію вводить в оману - PHP не підтримує названі аргументи. Насправді це відбувається встановлення цих трьох змінних, а потім негайно передавання їх значень функції. Це спричинить проблеми, якщо ці три змінні вже визначені в локальній області, оскільки вони будуть перезаписані.
Yi Jiang

11
@ yi-jiang, це лише найкоротший спосіб пояснити значення додаткових аргументів. Це більш самоопісательний то більш чистий варіант: $this->assertEquals($array1, $array2, "\$canonicalize = true", 0.0, 10, true);. Я міг би використовувати 4 рядки замість 1, але цього не робив.
прияжники

8
Ви не зазначаєте, що це рішення відкине ключі.
Одалрік,

8
зауважте, що $canonicalizeбуде видалено: github.com/sebastianbergmann/phpunit/isissue/3342 та assertEqualsCanonicalizing()замінить його.
koen

35

Моя проблема полягала в тому, що у мене було 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));


Проблема цього підходу полягає в тому, що якщо $array1має більше значень ніж $array2, то він повертає порожній масив, навіть якщо значення масиву не рівні. Ви також повинні перевірити, що розмір масиву однаковий, щоб бути впевненим.
petrkotek

3
Ви повинні зробити 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))
GordonM

2
assertEmptyне буде друкувати масив, якщо він не порожній, що незручно під час тестів налагодження. Я б запропонував використовувати:, $this->assertSame(array_diff($expected, $actual), array_diff($actual, $expected), $message);оскільки це надрукує найкорисніше повідомлення про помилку з мінімумом додаткового коду. Це працює, тому що A \ B = B \ A ⇔ A \ B і B \ A порожні ⇔ A = B
Denilson Sá Maia

Зауважте, що array_diff перетворює кожне значення в рядок для порівняння.
Костянтин Пелепелін

Щоб додати до @checat: ви отримаєте Array to string conversionповідомлення, коли спробуєте передати масив до рядка. Спосіб подолати це за допомогоюimplode
ub3rst4r

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

Якщо будь-який масив містить об'єкти, json_encode кодує лише загальнодоступні властивості. Це все ще спрацює, але лише в тому випадку, якщо всі властивості, що визначають рівність, публічні. Погляньте на наступний інтерфейс для управління json_encoding приватних властивостей. php.net/manual/en/class.jsonserializable.php
Westy92

1
Це працює навіть без сортування. Для assertEqualsзамовлення значення не має.
Заповіт

1
Дійсно, ми також можемо використовувати $this->assertSame($exp, $arr); подібне порівняння, оскільки $this->assertEquals(json_encode($exp), json_encode($arr)); різниця полягає лише в тому, що нам не доведеться використовувати json_encode
maxwells

15

Простий хелперний метод

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

8

Якщо масив сортується, я б сортував їх обидва, перш ніж перевірити рівність. Якщо ні, я б перетворив їх на деякі набори і порівняв би їх.


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

Або з 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)));

Це розумно :)
Крістіан

Саме те, що я шукав. Простий.
Абдул Майе

6

Хоча ви не дбаєте про замовлення, можливо, це буде простіше врахувати:

Спробуйте:

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

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

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

3

Дані рішення не зробили для мене завдання, тому що я хотів вміти обробляти багатовимірний масив і мати чітке повідомлення про те, що відрізняється між двома масивами.

Ось моя функція

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("/"));

1

Я написав простий код, щоб спочатку отримати всі ключі з багатовимірного масиву:

 /**
 * 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


0

Якщо значення - це лише int або рядки, а масиви декількох рівнів немає ...

Чому б не просто сортувати масиви, перетворити їх у рядок ...

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

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

... а потім порівняйте рядок:

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

-2

Якщо ви хочете перевірити лише значення масиву, що ви можете зробити:

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

1
На жаль, це не тестування "лише значень", але і значень, і порядку їх значень. Напр.echo("<pre>"); print_r(array_values(array("size" => "XL", "color" => "gold"))); print_r(array_values(array("color" => "gold", "size" => "XL")));
кишенькові та

-3

Інший варіант, як ніби у вас вже не вистачало, - це поєднати в assertArraySubsetпоєднанні з, assertCountщоб зробити своє твердження. Отже, ваш код виглядав би приблизно так.

self::assertCount(EXPECTED_NUM_ELEMENT, $array); self::assertArraySubset(SUBSET, $array);

Таким чином ви замовляєте незалежне, але все одно стверджуєте, що всі ваші елементи присутні.


У assertArraySubsetпорядку індекси мають значення, тому він не працюватиме. тобто self :: assertArraySubset (['a'], ['b', 'a']) буде помилковим, тому що [0 => 'a']немає всередині[0 => 'b', 1 => 'a']
Роберт Т.

Вибачте, але мені доведеться погодитися з Робертом. Спочатку я подумав, що це буде гарним рішенням порівняти масиви зі строковими клавішами, але assertEqualsвже обробляє це, якщо ключі не в одному порядку. Я просто тестував це.
Кодос Джонсон
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.