web-dev-qa-db-fra.com

PHPUnit: assert deux tableaux sont égaux, mais l'ordre des éléments n'est pas important

Quel est un bon moyen d'affirmer que deux tableaux d'objets sont égaux, lorsque l'ordre des éléments dans le tableau est sans importance, voire susceptible de changer?

108
koen

Le moyen le plus propre de le faire serait d'étendre phpunit avec une nouvelle méthode d'assertion Mais voici une idée pour un moyen plus simple pour le moment. Code non testé, veuillez vérifier:

Quelque part dans votre application:

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

Dans votre test:

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

La méthode assertEquals a un paramètre non documenté $ canonicalize. Si vous utilisez $ canonicalize = true, les tableaux seront triés par le comparateur de tableaux PHPUnit lui-même.

Exemple de code:

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

Code source des tableaux de comparaison dans la dernière version de PHPUnit: https://github.com/sebastianbergmann/comparator/blob/master/src/ArrayComparator.php#L43

150
pryazhnikov

Mon problème était que j'avais 2 tableaux (les clés de tableau ne sont pas pertinentes pour moi, juste les valeurs).

Par exemple, je voulais tester si 

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

avait le même contenu (ordre non pertinent pour moi) que

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

J'ai donc utilisé array_diff

Le résultat final était (si les tableaux sont égaux, la différence donnera un tableau vide). Veuillez noter que la différence est calculée dans les deux sens (Merci @beret, @GordonM)

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

Pour un message d'erreur plus détaillé (pendant le débogage), vous pouvez également tester comme ceci (grâce @ DenilsonSá):

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

Ancienne version avec des bugs à l'intérieur:

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

34
Valentin Despa

Une autre possibilité:

  1. Trier les deux tableaux
  2. Les convertir en chaîne
  3. Assert les deux chaînes sont égales 

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

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

$this->assertEquals(json_encode($exp), json_encode($arr));
18
rodrigo-silveira

Méthode d'assistance simple

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

Ou si vous avez besoin de plus d'informations de débogage lorsque les tableaux ne sont pas égaux

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

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

Si le tableau est triable, je trierais les deux avant de vérifier l’égalité. Sinon, je les convertirais en ensembles et les comparerais.

7
Rodney Gitzel

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

Ou avec 2 affirmations (plus faciles à lire):

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

Si les clés sont les mêmes mais hors d'usage, cela devrait résoudre le problème.

Vous devez juste obtenir les clés dans le même ordre et comparer les résultats.

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

Nous utilisons la méthode de wrapper suivante dans nos tests:

/**
 * 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
t.heintz

Même si vous ne vous souciez pas de la commande, il peut être plus facile de prendre cela en compte: 

Essayer:

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

Les solutions proposées ne me convenaient pas, car je souhaitais pouvoir gérer des tableaux multidimensionnels et avoir un message clair sur les différences entre les deux tableaux.

Voici ma fonction

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

Puis l'utiliser

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

J'ai écrit un code simple pour obtenir d'abord toutes les clés d'un tableau multidimensionnel:

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

Ensuite, pour vérifier qu’elles étaient structurées de la même manière quel que soit l’ordre des clés:

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

HTH

1
sturrockad

Si les valeurs ne sont que des entiers ou des chaînes, et qu’aucun tableau à plusieurs niveaux ...

Pourquoi ne pas simplement trier les tableaux, les convertir en chaîne ...

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

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

... puis comparez string:

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