Qual é uma boa maneira de afirmar que duas matrizes de objetos são iguais, quando a ordem dos elementos na matriz não é importante ou está sujeita a alterações?
Qual é uma boa maneira de afirmar que duas matrizes de objetos são iguais, quando a ordem dos elementos na matriz não é importante ou está sujeita a alterações?
Respostas:
A maneira mais limpa de fazer isso seria estender o phpunit com um novo método de asserção. Mas aqui está uma idéia para uma maneira mais simples por enquanto. Código não testado, verifique:
Em algum lugar do seu aplicativo:
/**
* 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;
}
No seu teste:
$this->assertTrue(arrays_are_similar($foo, $bar));
count(array_diff_assoc($b, $a))
também.
Você pode usar o método assertEqualsCanonicalizing que foi adicionado no PHPUnit 7.5. Se você comparar as matrizes usando esse método, essas matrizes serão classificadas pelo próprio comparador de matrizes PHPUnit.
Exemplo 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;
}
}
Nas versões mais antigas do PHPUnit, você pode usar um parâmetro não documentado, $ canonicalize of assertEquals . Se você passar $ canonicalize = true , obterá o mesmo efeito:
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-fonte do comparador de matrizes na versão mais recente do PHPUnit: https://github.com/sebastianbergmann/comparator/blob/master/src/ArrayComparator.php#L46
$delta = 0.0, $maxDepth = 10, $canonicalize = true
para passar parâmetros para a função é enganoso - o PHP não suporta argumentos nomeados. O que isso realmente está fazendo é definir essas três variáveis e passar imediatamente seus valores para a função. Isso causará problemas se essas três variáveis já estiverem definidas no escopo local, pois serão substituídas.
$this->assertEquals($array1, $array2, "\$canonicalize = true", 0.0, 10, true);
. Eu poderia usar 4 linhas em vez de 1, mas não fiz isso.
$canonicalize
será removido: github.com/sebastianbergmann/phpunit/issues/3342 e assertEqualsCanonicalizing()
o substituirá.
Meu problema era que eu tinha 2 matrizes (chaves de matriz não são relevantes para mim, apenas os valores).
Por exemplo, eu queria testar se
$expected = array("0" => "green", "2" => "red", "5" => "blue", "9" => "pink");
tinha o mesmo conteúdo (pedido não relevante para mim) que
$actual = array("0" => "pink", "1" => "green", "3" => "yellow", "red", "blue");
Então, eu usei array_diff .
O resultado final foi (se as matrizes forem iguais, a diferença resultará em uma matriz vazia). Observe que a diferença é calculada nos dois sentidos (Obrigado @beret, @GordonM)
$this->assertEmpty(array_merge(array_diff($expected, $actual), array_diff($actual, $expected)));
Para uma mensagem de erro mais detalhada (durante a depuração), você também pode testar desta forma (obrigado @ DenilsonSá):
$this->assertSame(array_diff($expected, $actual), array_diff($actual, $expected));
Versão antiga com erros dentro:
$ this-> assertEmpty (array_diff ($ array2, $ array1));
$array1
tiver mais valores que $array2
, retornará uma matriz vazia, mesmo que os valores da matriz não sejam iguais. Você também deve testar se o tamanho da matriz é o mesmo, para ter certeza.
$a1 = [1,2,3,4,5]; $a2 = [1,3,5]; var_dump (array_diff ($a1, $a2)); var_dump (array_diff ($a2, $a1))
assertEmpty
não imprimirá a matriz se não estiver vazia, o que é inconveniente durante a depuração de testes. Eu sugiro usar:, $this->assertSame(array_diff($expected, $actual), array_diff($actual, $expected), $message);
pois isso imprimirá a mensagem de erro mais útil com o mínimo de código extra. Isto funciona porque A \ B = B \ Uma ⇔ A \ B e B \ A são vazio ⇔ A = B
Array to string conversion
mensagem ao tentar converter uma matriz em uma string. Uma maneira de contornar isso é usandoimplode
Outra possibilidade:
$arr = array(23, 42, 108);
$exp = array(42, 23, 108);
sort($arr);
sort($exp);
$this->assertEquals(json_encode($exp), json_encode($arr));
assertEquals
o pedido não importa.
$this->assertSame($exp, $arr);
o que faz comparação semelhante como $this->assertEquals(json_encode($exp), json_encode($arr));
única diferença é que não tem que usar json_encode
Método auxiliar simples
protected function assertEqualsArrays($expected, $actual, $message) {
$this->assertTrue(count($expected) == count(array_intersect($expected, $actual)), $message);
}
Ou se você precisar de mais informações de depuração quando matrizes não forem iguais
protected function assertEqualsArrays($expected, $actual, $message) {
sort($expected);
sort($actual);
$this->assertEquals($expected, $actual, $message);
}
Se a matriz for classificável, eu os classificaria antes de verificar a igualdade. Caso contrário, eu os converteria em conjuntos de algum tipo e os compararia.
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)));
Ou com 2 afirmações (mais fáceis de ler):
// 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)));
Mesmo que você não se importe com o pedido, pode ser mais fácil levar isso em consideração:
Experimentar:
asort($foo);
asort($bar);
$this->assertEquals($foo, $bar);
Usamos o seguinte método de invólucro em nossos testes:
/**
* 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);
}
Se as chaves forem as mesmas, mas fora de ordem, isso deve resolver o problema.
Você só precisa obter as chaves na mesma ordem e comparar os 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);
}
As soluções fornecidas não funcionaram para mim porque eu queria poder lidar com uma matriz multidimensional e ter uma mensagem clara do que é diferente entre as duas matrizes.
Aqui está a minha função
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)."`.");
}
}
}
}
Então para usá-lo
$this->assertArrayEquals($array1, $array2, array("/"));
Eu escrevi um código simples para primeiro obter todas as chaves de uma 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;
}
Depois, para testar se eles foram estruturados da mesma maneira, independentemente da ordem das chaves:
$expectedKeys = $this->getAllArrayKeys($expectedData);
$actualKeys = $this->getAllArrayKeys($actualData);
$this->assertEmpty(array_diff($expectedKeys, $actualKeys));
HTH
Se os valores são apenas int ou strings, e nenhuma matriz de vários níveis ....
Por que não apenas classificar as matrizes, convertê-las em string ...
$mapping = implode(',', array_sort($myArray));
$list = implode(',', array_sort($myExpectedArray));
... e depois compare a string:
$this->assertEquals($myExpectedArray, $myArray);
Se você deseja testar apenas os valores da matriz, pode fazer:
$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")));
Outra opção, como se você já não tivesse o suficiente, é combinar assertArraySubset
combinado com assertCount
para fazer sua afirmação. Portanto, seu código seria algo parecido.
self::assertCount(EXPECTED_NUM_ELEMENT, $array);
self::assertArraySubset(SUBSET, $array);
Dessa forma, você é independente da ordem, mas ainda afirma que todos os seus elementos estão presentes.
assertArraySubset
ordem dos índices, importa para que não funcione. ou seja self :: assertArraySubset ([ 'a'], [ 'b', 'a']) será falso, porque [0 => 'a']
não está dentro[0 => 'b', 1 => 'a']
assertEquals
já lida com isso se as chaves não estiverem na mesma ordem. Eu apenas testei.