wake-up-neo.com

PHPUnit: Zwei Arrays sind gleich, aber die Reihenfolge der Elemente ist nicht wichtig

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?

108
koen

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));
32
Craig

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

150
pryazhnikov

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));

34
Valentin Despa

Eine andere Möglichkeit:

  1. Sortieren Sie beide Arrays
  2. Wandle sie in einen String um
  3. Bestätigen Sie, dass beide Zeichenfolgen gleich sind 

$arr = array(23, 42, 108);
$exp = array(42, 23, 108);

sort($arr);
sort($exp);

$this->assertEquals(json_encode($exp), json_encode($arr));
18

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);
}
13
ksimka

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.

7
Rodney Gitzel

array_diff () verwenden :

$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)));
6
caligari

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);
}
5
Cris

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);
}
5
t.heintz

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("/"));
2
moins52

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

1
sturrockad

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);
0
koalaok