¿Cuál es una buena manera de afirmar que dos conjuntos de objetos son iguales, cuando el orden de los elementos en el conjunto no es importante, o incluso está sujeto a cambios?
¿Cuál es una buena manera de afirmar que dos conjuntos de objetos son iguales, cuando el orden de los elementos en el conjunto no es importante, o incluso está sujeto a cambios?
Respuestas:
La forma más limpia de hacer esto sería extender phpunit con un nuevo método de afirmación. Pero aquí hay una idea de una manera más simple por ahora. Código no probado, verifique:
En algún lugar de tu aplicación:
/**
* 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;
}
En tu prueba:
$this->assertTrue(arrays_are_similar($foo, $bar));
count(array_diff_assoc($b, $a))
también.
Puede usar el método ClaimEqualsCanonicalizing que se agregó en PHPUnit 7.5. Si compara las matrices utilizando este método, estas matrices se ordenarán por el comparador de matrices PHPUnit.
Ejemplo de código:
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;
}
}
En versiones anteriores de PHPUnit, puede usar un parámetro no documentado $ canonicalize del método afirmarEquals . Si pasa $ canonicalize = true , obtendrá el mismo efecto:
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;
}
}
Código fuente del comparador de matrices en la última versión de PHPUnit: https://github.com/sebastianbergmann/comparator/blob/master/src/ArrayComparator.php#L46
$delta = 0.0, $maxDepth = 10, $canonicalize = true
pasar parámetros a la función es engañoso: PHP no admite argumentos con nombre. Lo que esto realmente está haciendo es establecer esas tres variables, luego pasar inmediatamente sus valores a la función. Esto causará problemas si esas tres variables ya están definidas en el ámbito local, ya que se sobrescribirán.
$this->assertEquals($array1, $array2, "\$canonicalize = true", 0.0, 10, true);
. Podría usar 4 líneas en lugar de 1, pero no hice eso.
$canonicalize
se eliminará: github.com/sebastianbergmann/phpunit/issues/3342 y assertEqualsCanonicalizing()
lo reemplazará.
Mi problema era que tenía 2 matrices (las claves de matriz no son relevantes para mí, solo los valores).
Por ejemplo, quería probar si
$expected = array("0" => "green", "2" => "red", "5" => "blue", "9" => "pink");
tenía el mismo contenido (orden no relevante para mí) como
$actual = array("0" => "pink", "1" => "green", "3" => "yellow", "red", "blue");
Así que he usado array_diff .
El resultado final fue (si las matrices son iguales, la diferencia dará como resultado una matriz vacía). Tenga en cuenta que la diferencia se calcula en ambos sentidos (Gracias @beret, @GordonM)
$this->assertEmpty(array_merge(array_diff($expected, $actual), array_diff($actual, $expected)));
Para un mensaje de error más detallado (durante la depuración), también puede probar de esta manera (gracias @ DenilsonSá):
$this->assertSame(array_diff($expected, $actual), array_diff($actual, $expected));
Versión antigua con errores dentro:
$ this-> afirmeEmpty (array_diff ($ array2, $ array1));
$array1
tiene más valores que $array2
, entonces devuelve una matriz vacía aunque los valores de la matriz no sean iguales. También debe probar, ese tamaño de matriz es el mismo, para estar seguro.
$a1 = [1,2,3,4,5]; $a2 = [1,3,5]; var_dump (array_diff ($a1, $a2)); var_dump (array_diff ($a2, $a1))
assertEmpty
no imprimirá la matriz si no está vacía, lo cual es inconveniente al depurar las pruebas. Sugeriría usar: $this->assertSame(array_diff($expected, $actual), array_diff($actual, $expected), $message);
ya que esto imprimirá el mensaje de error más útil con el mínimo de código adicional. Esto funciona porque A \ B = B \ A ⇔ A \ B y B \ A están vacías ⇔ A = B
Array to string conversion
mensaje cuando intente convertir una matriz en una cadena. Una forma de evitar esto es usandoimplode
Otra posibilidad:
$arr = array(23, 42, 108);
$exp = array(42, 23, 108);
sort($arr);
sort($exp);
$this->assertEquals(json_encode($exp), json_encode($arr));
assertEquals
el orden no importa.
$this->assertSame($exp, $arr);
una comparación similar, ya que la $this->assertEquals(json_encode($exp), json_encode($arr));
única diferencia es que no tenemos que usar json_encode
Método de ayuda simple
protected function assertEqualsArrays($expected, $actual, $message) {
$this->assertTrue(count($expected) == count(array_intersect($expected, $actual)), $message);
}
O si necesita más información de depuración cuando las matrices no son iguales
protected function assertEqualsArrays($expected, $actual, $message) {
sort($expected);
sort($actual);
$this->assertEquals($expected, $actual, $message);
}
Si la matriz es ordenable, los ordenaría a ambos antes de verificar la igualdad. Si no, los convertiría en conjuntos de algún tipo y los compararía.
Usando 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)));
O con 2 afirmaciones (más fácil de leer):
// 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)));
Aunque no le importa el pedido, puede ser más fácil tenerlo en cuenta:
Tratar:
asort($foo);
asort($bar);
$this->assertEquals($foo, $bar);
Utilizamos el siguiente método de envoltura en nuestras Pruebas:
/**
* 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 las claves son las mismas pero fuera de servicio, esto debería resolverlo.
Solo tiene que obtener las claves en el mismo orden y comparar los resultados.
/**
* 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);
}
Las soluciones dadas no hicieron el trabajo por mí porque quería ser capaz de manejar una matriz multidimensional y tener un mensaje claro de lo que es diferente entre las dos matrices.
Aqui esta mi funcion
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)."`.");
}
}
}
}
Entonces para usarlo
$this->assertArrayEquals($array1, $array2, array("/"));
Escribí un código simple para obtener primero todas las claves de una matriz multidimensional:
/**
* 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;
}
Luego, para comprobar que estaban estructurados de la misma manera, independientemente del orden de las teclas:
$expectedKeys = $this->getAllArrayKeys($expectedData);
$actualKeys = $this->getAllArrayKeys($actualData);
$this->assertEmpty(array_diff($expectedKeys, $actualKeys));
HTH
Si los valores son solo int o cadenas, y no hay matrices de niveles múltiples ...
¿Por qué no simplemente ordenar las matrices, convertirlas en cadenas ...
$mapping = implode(',', array_sort($myArray));
$list = implode(',', array_sort($myExpectedArray));
... y luego compara la cadena:
$this->assertEquals($myExpectedArray, $myArray);
Si desea probar solo los valores de la matriz que puede hacer:
$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")));
Otra opción, como si no tuviera ya suficiente, es combinar assertArraySubset
combinado con assertCount
para hacer su afirmación. Entonces, su código se vería algo así.
self::assertCount(EXPECTED_NUM_ELEMENT, $array);
self::assertArraySubset(SUBSET, $array);
De esta manera, usted es independiente del orden pero aún así afirma que todos sus elementos están presentes.
assertArraySubset
el orden de los índices importa, por lo que no funcionará. es decir, self :: afirmarArraySubset (['a'], ['b', 'a']) será falso, porque [0 => 'a']
no está adentro[0 => 'b', 1 => 'a']
assertEquals
ya lo maneja si las claves no están en el mismo orden. Acabo de probarlo.