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, ou même sujet à changement?
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, ou même sujet à changement?
Réponses:
La façon la plus propre de faire cela 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));
count(array_diff_assoc($b, $a))
également vérifier .
Vous pouvez utiliser la méthode assertEqualsCanonicalizing qui a été ajoutée dans PHPUnit 7.5. Si vous comparez les tableaux en utilisant cette méthode, ces 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->assertEqualsCanonicalizing($array1, $array2);
// Fail
$this->assertEquals($array1, $array2);
}
private function getObject($value)
{
$result = new \stdClass();
$result->property = $value;
return $result;
}
}
Dans les anciennes versions de PHPUnit, vous pouvez utiliser un paramètre non documenté $ canonicalize de la méthode assertEquals . Si vous passez $ canonicalize = true , vous obtiendrez le même effet:
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 du comparateur de tableaux à la dernière version de PHPUnit: https://github.com/sebastianbergmann/comparator/blob/master/src/ArrayComparator.php#L46
$delta = 0.0, $maxDepth = 10, $canonicalize = true
pour passer des paramètres dans la fonction est trompeur - PHP ne prend pas en charge les arguments nommés. Ce que cela fait en fait est de définir ces trois variables, puis de transmettre immédiatement leurs valeurs à la fonction. Cela posera des problèmes si ces trois variables sont déjà définies dans la portée locale puisqu'elles seront écrasées.
$this->assertEquals($array1, $array2, "\$canonicalize = true", 0.0, 10, true);
. Je pourrais utiliser 4 lignes au lieu de 1, mais je ne l'ai pas fait.
$canonicalize
cela sera supprimé: github.com/sebastianbergmann/phpunit/issues/3342 et assertEqualsCanonicalizing()
le remplacera.
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 se traduira par 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é (lors du débogage), vous pouvez également tester comme ceci (merci @ 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));
$array1
a plus de valeurs que $array2
, alors il renvoie un tableau vide même si les valeurs du tableau ne sont pas égales. Vous devriez également tester, que la taille du tableau est la même, pour être sûr.
$a1 = [1,2,3,4,5]; $a2 = [1,3,5]; var_dump (array_diff ($a1, $a2)); var_dump (array_diff ($a2, $a1))
assertEmpty
n'imprimera pas le tableau s'il n'est pas vide, ce qui n'est pas pratique lors du débogage des tests. Je suggère d'utiliser:, $this->assertSame(array_diff($expected, $actual), array_diff($actual, $expected), $message);
car cela imprimera le message d'erreur le plus utile avec le minimum de code supplémentaire. Cela fonctionne parce que A \ B = B \ A ⇔ A \ B et B \ A sont vides ⇔ A = B
Array to string conversion
message lorsque vous tenterez de convertir un tableau en chaîne. Un moyen de contourner ce implode
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));
assertEquals
l'ordre n'a pas d'importance.
$this->assertSame($exp, $arr);
qui ne comparaison similaire que $this->assertEquals(json_encode($exp), json_encode($arr));
seule différence est que nous ne devons pas utiliser json_encode
Méthode d'aide 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 les trierais tous les deux avant de vérifier l'égalité. Sinon, je les convertirais en ensembles d'une sorte et les comparerais.
En utilisant 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 assertions (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)));
Même si vous ne vous souciez pas de la commande, il peut être plus facile d'en tenir compte:
Essayer:
asort($foo);
asort($bar);
$this->assertEquals($foo, $bar);
Nous utilisons la méthode 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);
}
Si les clés sont les mêmes mais dans le désordre, cela devrait le résoudre.
Il vous suffit de récupérer les clés dans le même ordre et de 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);
}
Les solutions données n'ont pas fait le travail pour moi parce que je voulais être capable de gérer des tableaux multidimensionnels et d'avoir un message clair sur ce qui est différent 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)."`.");
}
}
}
}
Alors pour 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 tester qu'ils étaient structurés 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 int ou des chaînes, et pas de tableaux à 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 la chaîne:
$this->assertEquals($myExpectedArray, $myArray);
Si vous voulez tester uniquement les valeurs du tableau, vous pouvez faire:
$this->assertEquals(array_values($arrayOne), array_values($arrayTwo));
echo("<pre>"); print_r(array_values(array("size" => "XL", "color" => "gold"))); print_r(array_values(array("color" => "gold", "size" => "XL")));
Une autre option, comme si vous n'en aviez pas déjà assez, est de combiner assertArraySubset
avec assertCount
pour faire votre affirmation. Donc, votre code ressemblerait à quelque chose comme.
self::assertCount(EXPECTED_NUM_ELEMENT, $array);
self::assertArraySubset(SUBSET, $array);
De cette façon, vous êtes indépendant de l'ordre tout en affirmant que tous vos éléments sont présents.
assertArraySubset
l'ordre des index, cela ne fonctionnera pas. ie self :: assertArraySubset (['a'], ['b', 'a']) sera faux, car il [0 => 'a']
n'est pas à l'intérieur[0 => 'b', 1 => 'a']
assertEquals
gère déjà cela si les clés ne sont pas dans le même ordre. Je viens de le tester.