Was ist ein guter Weg, um zu behaupten, dass zwei Arrays von Objekten gleich sind, wenn die Reihenfolge der Elemente im Array unwichtig ist oder sich sogar ändern kann?
Was ist ein guter Weg, um zu behaupten, dass zwei Arrays von Objekten gleich sind, wenn die Reihenfolge der Elemente im Array unwichtig ist oder sich sogar ändern kann?
Antworten:
Der sauberste Weg, dies zu tun, wäre, phpunit mit einer neuen Assertionsmethode zu erweitern. Aber hier ist eine Idee für einen einfacheren Weg. Ungetesteter Code, bitte überprüfen Sie:
Irgendwo in Ihrer 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;
}
In Ihrem Test:
$this->assertTrue(arrays_are_similar($foo, $bar));
count(array_diff_assoc($b, $a))
.
Sie können die in PHPUnit 7.5 hinzugefügte assertEqualsCanonicalizing- Methode verwenden. Wenn Sie die Arrays mit dieser Methode vergleichen, werden diese Arrays nach dem PHPUnit-Arrays-Komparator selbst sortiert.
Codebeispiel:
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;
}
}
In älteren Versionen von PHPUnit können Sie einen undokumentierten Parameter $ canonicalize der assertEquals- Methode verwenden. Wenn Sie $ canonicalize = true übergeben , erhalten Sie den gleichen Effekt:
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;
}
}
Arrays-Komparator-Quellcode in der neuesten Version von PHPUnit: https://github.com/sebastianbergmann/comparator/blob/master/src/ArrayComparator.php#L46
$delta = 0.0, $maxDepth = 10, $canonicalize = true
Übergabe von Parametern an die Funktion ist irreführend - PHP unterstützt keine benannten Argumente. Dabei werden diese drei Variablen festgelegt und ihre Werte sofort an die Funktion übergeben. Dies führt zu Problemen, wenn diese drei Variablen bereits im lokalen Bereich definiert sind, da sie überschrieben werden.
$this->assertEquals($array1, $array2, "\$canonicalize = true", 0.0, 10, true);
. Ich könnte 4 Zeilen anstelle von 1 verwenden, aber das habe ich nicht getan.
$canonicalize
dass entfernt wird: github.com/sebastianbergmann/phpunit/issues/3342 und assertEqualsCanonicalizing()
ersetzt.
Mein Problem war, dass ich 2 Arrays hatte (Array-Schlüssel sind für mich nicht relevant, nur die Werte).
Zum Beispiel wollte ich testen, ob
$expected = array("0" => "green", "2" => "red", "5" => "blue", "9" => "pink");
hatte den gleichen Inhalt (Reihenfolge für mich nicht relevant) wie
$actual = array("0" => "pink", "1" => "green", "3" => "yellow", "red", "blue");
Also habe ich array_diff verwendet .
Das Endergebnis war (wenn die Arrays gleich sind, führt die Differenz zu einem leeren Array). Bitte beachten Sie, dass der Unterschied in beide Richtungen berechnet wird (Danke @beret, @GordonM).
$this->assertEmpty(array_merge(array_diff($expected, $actual), array_diff($actual, $expected)));
Für eine detailliertere Fehlermeldung (beim Debuggen) können Sie auch wie folgt testen (danke @ DenilsonSá):
$this->assertSame(array_diff($expected, $actual), array_diff($actual, $expected));
Alte Version mit Fehlern im Inneren:
$ this-> assertEmpty (array_diff ($ array2, $ array1));
$array1
mehr Werte als vorhanden sind $array2
, ein leeres Array zurückgegeben wird, obwohl die Array-Werte nicht gleich sind. Sie sollten auch testen, ob die Arraygröße gleich ist, um sicherzugehen.
$a1 = [1,2,3,4,5]; $a2 = [1,3,5]; var_dump (array_diff ($a1, $a2)); var_dump (array_diff ($a2, $a1))
assertEmpty
druckt das Array nicht, wenn es nicht leer ist, was beim Debuggen von Tests unpraktisch ist. Ich würde vorschlagen,: zu verwenden $this->assertSame(array_diff($expected, $actual), array_diff($actual, $expected), $message);
, da dies die nützlichste Fehlermeldung mit einem Minimum an zusätzlichem Code druckt. Dies funktioniert, weil A \ B = B \ A ⇔ A \ B und B \ A leer sind ⇔ A = B
Array to string conversion
Nachricht, wenn Sie versuchen, ein Array in eine Zeichenfolge umzuwandeln . Eine Möglichkeit, dies zu implode
Eine andere Möglichkeit:
$arr = array(23, 42, 108);
$exp = array(42, 23, 108);
sort($arr);
sort($exp);
$this->assertEquals(json_encode($exp), json_encode($arr));
assertEquals
die Bestellung spielt das keine Rolle.
$this->assertSame($exp, $arr);
ähnlichen Vergleich verwenden, da der $this->assertEquals(json_encode($exp), json_encode($arr));
einzige Unterschied darin besteht, dass wir json_encode
Einfache Hilfsmethode
protected function assertEqualsArrays($expected, $actual, $message) {
$this->assertTrue(count($expected) == count(array_intersect($expected, $actual)), $message);
}
Oder wenn Sie mehr Debug-Informationen benötigen, wenn Arrays nicht gleich sind
protected function assertEqualsArrays($expected, $actual, $message) {
sort($expected);
sort($actual);
$this->assertEquals($expected, $actual, $message);
}
Wenn das Array sortierbar ist, würde ich beide sortieren, bevor ich die Gleichheit überprüfe. Wenn nicht, würde ich sie in Sätze umwandeln und diese vergleichen.
Verwenden von 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)));
Oder mit 2 Behauptungen (leichter zu lesen):
// 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)));
Auch wenn Sie sich nicht für die Bestellung interessieren, ist es möglicherweise einfacher, dies zu berücksichtigen:
Versuchen:
asort($foo);
asort($bar);
$this->assertEquals($foo, $bar);
In unseren Tests verwenden wir die folgende Wrapper-Methode:
/**
* 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);
}
Wenn die Schlüssel gleich, aber nicht in Ordnung sind, sollte dies das Problem lösen.
Sie müssen nur die Schlüssel in derselben Reihenfolge erhalten und die Ergebnisse vergleichen.
/**
* 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);
}
Die angegebenen Lösungen haben für mich nicht funktioniert, weil ich in der Lage sein wollte, mehrdimensionale Arrays zu verarbeiten und eine klare Botschaft darüber zu erhalten, was zwischen den beiden Arrays unterschiedlich ist.
Hier ist meine Funktion
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)."`.");
}
}
}
}
Dann, um es zu benutzen
$this->assertArrayEquals($array1, $array2, array("/"));
Ich habe einen einfachen Code geschrieben, um zuerst alle Schlüssel aus einem mehrdimensionalen Array zu erhalten:
/**
* 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;
}
Um dann zu testen, ob sie unabhängig von der Reihenfolge der Schlüssel gleich aufgebaut sind:
$expectedKeys = $this->getAllArrayKeys($expectedData);
$actualKeys = $this->getAllArrayKeys($actualData);
$this->assertEmpty(array_diff($expectedKeys, $actualKeys));
HTH
Wenn die Werte nur int oder Strings sind und keine Arrays mit mehreren Ebenen ....
Warum nicht einfach die Arrays sortieren, sie in einen String konvertieren ...
$mapping = implode(',', array_sort($myArray));
$list = implode(',', array_sort($myExpectedArray));
... und dann String vergleichen:
$this->assertEquals($myExpectedArray, $myArray);
Wenn Sie nur die Werte des Arrays testen möchten, können Sie Folgendes tun:
$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")));
Eine andere Möglichkeit, als ob Sie noch nicht genug hätten, ist die assertArraySubset
Kombination mit assertCount
, um Ihre Behauptung aufzustellen. Ihr Code würde also ungefähr so aussehen.
self::assertCount(EXPECTED_NUM_ELEMENT, $array);
self::assertArraySubset(SUBSET, $array);
Auf diese Weise sind Sie auftragsunabhängig und behaupten dennoch, dass alle Ihre Elemente vorhanden sind.
assertArraySubset
der Reihenfolge der Indizes ist es wichtig, dass es nicht funktioniert. dh self :: assertArraySubset (['a'], ['b', 'a']) wird falsch sein, weil [0 => 'a']
es nicht drinnen ist[0 => 'b', 1 => 'a']
assertEquals
bereits erledigt, wenn die Schlüssel nicht in derselben Reihenfolge sind. Ich habe es gerade getestet.