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?
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));
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
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));
Une autre possibilité:
$arr = array(23, 42, 108);
$exp = array(42, 23, 108);
sort($arr);
sort($exp);
$this->assertEquals(json_encode($exp), json_encode($arr));
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);
}
Si le tableau est triable, je trierais les deux avant de vérifier l’égalité. Sinon, je les convertirais en ensembles et les comparerais.
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)));
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);
}
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);
}
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);
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("/"));
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
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);