Qual è un buon modo per affermare che due matrici di oggetti sono uguali, quando l'ordine degli elementi nell'array è irrilevante o addirittura soggetto a modifiche?
Qual è un buon modo per affermare che due matrici di oggetti sono uguali, quando l'ordine degli elementi nell'array è irrilevante o addirittura soggetto a modifiche?
Risposte:
Il modo più pulito per farlo sarebbe estendere phpunit con un nuovo metodo di asserzione. Ma ecco un'idea per un modo più semplice per ora. Codice non testato, si prega di verificare:
Da qualche parte nella tua app:
/**
* 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;
}
Nel tuo test:
$this->assertTrue(arrays_are_similar($foo, $bar));
count(array_diff_assoc($b, $a))
anche.
Puoi usare il metodo assertEqualsCanonicalizing che è stato aggiunto in PHPUnit 7.5. Se si confrontano le matrici usando questo metodo, queste matrici saranno ordinate dallo stesso comparatore di matrici PHPUnit.
Esempio di codice:
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;
}
}
Nelle versioni precedenti di PHPUnit è possibile utilizzare un parametro non documentato $ canonicalize del metodo assertEquals . Se passi $ canonicalize = true , otterrai lo stesso effetto:
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;
}
}
Codice sorgente del comparatore di array all'ultima versione di PHPUnit: https://github.com/sebastianbergmann/comparator/blob/master/src/ArrayComparator.php#L46
$delta = 0.0, $maxDepth = 10, $canonicalize = true
di passare parametri nella funzione è fuorviante - PHP non supporta argomenti con nome. Quello che sta effettivamente facendo è impostare queste tre variabili, quindi passare immediatamente i loro valori alla funzione. Ciò causerà problemi se queste tre variabili sono già definite nell'ambito locale poiché verranno sovrascritte.
$this->assertEquals($array1, $array2, "\$canonicalize = true", 0.0, 10, true);
. Potrei usare 4 righe invece di 1, ma non l'ho fatto.
$canonicalize
verrà rimosso: github.com/sebastianbergmann/phpunit/issues/3342 e assertEqualsCanonicalizing()
lo sostituirà.
Il mio problema era che avevo 2 array (le chiavi dell'array non sono rilevanti per me, ma solo i valori).
Ad esempio, volevo testare se
$expected = array("0" => "green", "2" => "red", "5" => "blue", "9" => "pink");
aveva lo stesso contenuto (ordine non pertinente per me) di
$actual = array("0" => "pink", "1" => "green", "3" => "yellow", "red", "blue");
Quindi ho usato array_diff .
Il risultato finale è stato (se gli array sono uguali, la differenza si tradurrà in un array vuoto). Si noti che la differenza viene calcolata in entrambi i modi (grazie @beret, @GordonM)
$this->assertEmpty(array_merge(array_diff($expected, $actual), array_diff($actual, $expected)));
Per un messaggio di errore più dettagliato (durante il debug), puoi anche provare in questo modo (grazie @ DenilsonSá):
$this->assertSame(array_diff($expected, $actual), array_diff($actual, $expected));
Vecchia versione con bug interni:
$ this-> assertEmpty (array_diff ($ array2, $ array1));
$array1
ha più valori di $array2
, allora restituisce array vuoto anche se i valori dell'array non sono uguali. Dovresti anche testare, quella dimensione dell'array è la stessa, per essere sicuro.
$a1 = [1,2,3,4,5]; $a2 = [1,3,5]; var_dump (array_diff ($a1, $a2)); var_dump (array_diff ($a2, $a1))
assertEmpty
non stamperà l'array se non è vuoto, il che è scomodo durante i test di debug. Suggerirei di usare:, $this->assertSame(array_diff($expected, $actual), array_diff($actual, $expected), $message);
poiché questo stamperà il messaggio di errore più utile con il minimo di codice aggiuntivo. Questo funziona perché A \ B = B \ A ⇔ A \ B e B \ A sono vuoti ⇔ A = B
Array to string conversion
messaggio quando si tenta di eseguire il cast di un array in una stringa. Un modo per aggirare il problema è usareimplode
Un'altra 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'ordine non importa.
$this->assertSame($exp, $arr);
che fa il confronto simile come $this->assertEquals(json_encode($exp), json_encode($arr));
unica differenza è che non dobbiamo usare json_encode
Metodo di supporto semplice
protected function assertEqualsArrays($expected, $actual, $message) {
$this->assertTrue(count($expected) == count(array_intersect($expected, $actual)), $message);
}
O se sono necessarie ulteriori informazioni di debug quando gli array non sono uguali
protected function assertEqualsArrays($expected, $actual, $message) {
sort($expected);
sort($actual);
$this->assertEquals($expected, $actual, $message);
}
Se l'array è ordinabile, li ordinerei entrambi prima di verificare l'uguaglianza. Altrimenti, li convertirei in set di qualche tipo e li confronterei.
Utilizzando 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 assert (più facili da leggere):
// 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)));
Anche se non ti interessa l'ordine, potrebbe essere più semplice tenerne conto:
Provare:
asort($foo);
asort($bar);
$this->assertEquals($foo, $bar);
Nei nostri test utilizziamo il seguente metodo wrapper:
/**
* 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 le chiavi sono uguali ma non funzionanti, ciò dovrebbe risolverlo.
Devi solo ottenere le chiavi nello stesso ordine e confrontare i risultati.
/**
* 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);
}
Le soluzioni fornite non hanno funzionato per me perché volevo essere in grado di gestire array multidimensionali e avere un messaggio chiaro di ciò che è diverso tra i due array.
Ecco la mia funzione
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)."`.");
}
}
}
}
Quindi usarlo
$this->assertArrayEquals($array1, $array2, array("/"));
Ho scritto un codice semplice per ottenere prima tutte le chiavi da un array multidimensionale:
/**
* 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;
}
Quindi per verificare che fossero strutturati allo stesso modo indipendentemente dall'ordine delle chiavi:
$expectedKeys = $this->getAllArrayKeys($expectedData);
$actualKeys = $this->getAllArrayKeys($actualData);
$this->assertEmpty(array_diff($expectedKeys, $actualKeys));
HTH
Se i valori sono solo int o stringhe e nessuna matrice a più livelli ....
Perché non solo ordinare le matrici, convertirle in stringhe ...
$mapping = implode(',', array_sort($myArray));
$list = implode(',', array_sort($myExpectedArray));
... e poi confronta la stringa:
$this->assertEquals($myExpectedArray, $myArray);
Se vuoi testare solo i valori dell'array puoi fare:
$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")));
Un'altra opzione, come se non ne avessi già abbastanza, è combinare in assertArraySubset
combinazione con assertCount
per fare la tua affermazione. Quindi, il tuo codice sarebbe simile.
self::assertCount(EXPECTED_NUM_ELEMENT, $array);
self::assertArraySubset(SUBSET, $array);
In questo modo sei indipendente dall'ordine ma affermi comunque che tutti i tuoi elementi sono presenti.
assertArraySubset
l'ordine degli indici materia così non funzionerà. cioè self :: assertArraySubset (['a'], ['b', 'a']) sarà falso, perché [0 => 'a']
non è dentro[0 => 'b', 1 => 'a']
assertEquals
già lo gestisce se le chiavi non sono nello stesso ordine. L'ho appena provato.