Wie kann man gut behaupten, dass zwei Arrays von Objekten gleich sind, wenn die Reihenfolge der Elemente im Array unwichtig ist oder sogar geändert werden kann?
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:
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 deinem Test:
$this->assertTrue(arrays_are_similar($foo, $bar));
Die Methode assertEquals hat einen undokumentierten Parameter $ canonicalize. Wenn Sie $ canonicalize = true verwenden, werden die Arrays nach dem PHPUnit-Array-Komparator selbst sortiert.
Code-Beispiel:
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 Comparator-Quellcode in der neuesten Version von PHPUnit: https://github.com/sebastianbergmann/comparator/blob/master/src/ArrayComparator.php#L43
Mein Problem war, dass ich 2 Arrays hatte (Arrayschlü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 nicht relevant für mich) 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 der Unterschied 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 so 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));
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));
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 Sie die Gleichheit prüfen. Wenn nicht, würde ich sie in Mengen konvertieren und diese vergleichen.
$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 Asserts (einfacher 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)));
Wenn die Schlüssel gleich sind, aber nicht in Ordnung sind, sollte dies gelöst werden.
Sie müssen nur die Schlüssel in der gleichen 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);
}
In unseren Tests verwenden wir 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);
}
Auch wenn Sie sich nicht um die Bestellung kümmern, kann es einfacher sein, dies zu berücksichtigen:
Versuchen:
asort($foo);
asort($bar);
$this->assertEquals($foo, $bar);
Die angegebenen Lösungen haben mir nicht geholfen, weil ich mit mehrdimensionalen Arrays umgehen wollte und eine klare Aussage darüber hatte, 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 verwenden Sie es
$this->assertArrayEquals($array1, $array2, array("/"));
Ich habe einfachen Code geschrieben, um zunächst 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;
}
Dann testen Sie, 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, in String konvertieren ...
$mapping = implode(',', array_sort($myArray));
$list = implode(',', array_sort($myExpectedArray));
... und dann String vergleichen:
$this->assertEquals($myExpectedArray, $myArray);