PHPUnit:2つの配列が等しいことをアサートしますが、要素の順序は重要ではありません


132

配列内の要素の順序が重要ではない、または変更される可能性がある場合に、オブジェクトの2つの配列が等しいと断言するための良い方法は何ですか?


配列内のオブジェクトが等しくなることを気にしますか、それとも両方の配列にx個のオブジェクトyがあることを気にしますか?
エドリアン

@edorian両方が最も興味深いでしょう。私の場合では、各配列に1つのオブジェクトyしかありません。
公園

等しいと定義してください。ソートされたオブジェクトのハッシュを比較することは何が必要ですか?とにかくオブジェクトソートする必要があるでしょう。
takeshin

@takeshin ==と同じです。私の場合、それらは値オブジェクトなので、同一性は必要ありません。おそらくカスタムのassertメソッドを作成できます。私がそれに必要とするのは、各配列の要素の数を数えることであり、両方の要素ごとに等しい(==)が存在する必要があります。
公園

7
実際、PHPUnit 3.7.24では、$ this-> assertEqualsは配列に同じキーと値が含まれていることをアサートします。順序は関係ありません。
デレクソン2014

回答:


38

これを行う最もクリーンな方法は、新しいアサーションメソッドでphpunitを拡張することです。しかし、ここでは今のところより簡単な方法のアイデアがあります。テストされていないコード、確認してください:

アプリのどこかに:

 /**
 * 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;
}

あなたのテストでは:

$this->assertTrue(arrays_are_similar($foo, $bar));

クレイグ、あなたは私が最初に試みたものに近い。実際にはarray_diffが必要でしたが、オブジェクトに対しては機能しないようです。ここで説明するように、カスタムアサーションを記述しました:phpunit.de/manual/current/en/extending-phpunit.html
koen

現在の適切なリンクはhttpsあり、wwwなしです:phpunit.de/manual/current/en/extending-phpunit.html
Xavi Montero

foreach部分は不要です-array_diff_assocはすでにキーと値の両方を比較しています。編集:そして、あなたcount(array_diff_assoc($b, $a))もチェックする必要があります。
JohnSmith、2018

212

PHPUnit 7.5で追加されたassertEqualsCanonicalizingメソッドを使用できます。このメソッドを使用して配列を比較すると、これらの配列はPHPUnit配列コンパレータ自体によって並べ替えられます。

コード例:

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

PHPUnitの以前のバージョンでは、ドキュメントに記載されていないassertEqualsメソッドの$ canonicalizeパラメータを使用できます。$ canonicalize = trueを渡すと、同じ効果が得られます。

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

PHPUnitの最新バージョンの配列コンパレータソースコード:https : //github.com/sebastianbergmann/comparator/blob/master/src/ArrayComparator.php#L46


10
素晴らしい。なぜこれは受け入れられない答えです、@ koen?
rinogo 2015

7
$delta = 0.0, $maxDepth = 10, $canonicalize = true関数にパラメータを渡すためにを使用すると誤解を招く-PHPは名前付き引数をサポートしません。これが実際に行っていることは、これらの3つの変数を設定し、すぐにそれらの値を関数に渡すことです。これらの3つの変数はローカルスコープで既に定義されている場合、上書きされるため、問題が発生します。
Yi Jiang

11
@ yi-jiang、それは追加の引数の意味を説明するための最短の方法にすぎません。自己記述的でクリーンなバリアントです$this->assertEquals($array1, $array2, "\$canonicalize = true", 0.0, 10, true);。1行ではなく4行を使用することもできましたが、そうしませんでした。
pryazhnikov 2015年

8
このソリューションがキーを破棄することを指摘しないでください。
Odalrick

8
$canonicalize削除されることに注意してください:github.com/sebastianbergmann/phpunit/issues/3342そしてそれassertEqualsCanonicalizing()を置き換えます。
公園

35

私の問題は、2つの配列があったことです(配列キーは私には関係がなく、値だけが関係します)。

たとえば、私はテストしたかった

$expected = array("0" => "green", "2" => "red", "5" => "blue", "9" => "pink");

と同じ内容(私には関係のない順序)

$actual = array("0" => "pink", "1" => "green", "3" => "yellow", "red", "blue");

だから私はarray_diffを使用しました

最終結果は(配列が等しい場合、違いは空の配列になります)でした。違いは両方の方法で計算されることに注意してください(ありがとう@ beret、@ GordonM)

$this->assertEmpty(array_merge(array_diff($expected, $actual), array_diff($actual, $expected)));

(デバッグ中の)より詳細なエラーメッセージについては、次のようにテストすることもできます(@DenilsonSáに感謝)。

$this->assertSame(array_diff($expected, $actual), array_diff($actual, $expected));

内部にバグがある古いバージョン:

$ this-> assertEmpty(array_diff($ array2、$ array1));


このアプローチの問題は、の$array1値がより大きい場合$array2、配列の値が等しくなくても空の配列を返すことです。また、配列サイズが同じであることを確認するためにテストする必要があります。
petrkotek 2013

3
array_diffまたはarray_diff_assocを両方の方法で実行する必要があります。一方の配列がもう一方のスーパーセットである場合、一方向のarray_diffは空になりますが、もう一方の方向は空ではありません。 $a1 = [1,2,3,4,5]; $a2 = [1,3,5]; var_dump (array_diff ($a1, $a2)); var_dump (array_diff ($a2, $a1))
GordonM 2014年

2
assertEmpty空でない場合、配列は出力されません。これは、テストのデバッグ中に不便です。$this->assertSame(array_diff($expected, $actual), array_diff($actual, $expected), $message);これを使用することをお勧めします。これにより、最小限の追加コードで最も役立つエラーメッセージが出力されます。これは、A \ B = B \ A⇔A \ BおよびB \ Aが空であるため機能します
⇔A

array_diffは、比較のためにすべての値を文字列に変換することに注意してください。
Konstantin Pelepelin 2014

@checatに追加するにはArray to string conversion、配列を文字列にキャストしようとするとメッセージが表示されます。これを回避する方法は、次を使用することですimplode
ub3rst4r '11 / 12/16

20

その他の可能性:

  1. 両方の配列を並べ替える
  2. それらを文字列に変換します
  3. 両方の文字列が等しいとアサートする

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

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

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

いずれかの配列にオブジェクトが含まれている場合、json_encodeはパブリックプロパティのみをエンコードします。これは引き続き機能しますが、同等性を決定するすべてのプロパティがパブリックである場合のみです。プライベートプロパティのjson_encodingを制御するには、次のインターフェイスをご覧ください。php.net/manual/en/class.jsonserializable.php
Westy92

1
これは並べ替えなしでも機能します。以下のためにassertEquals順序は重要ではありません。
Wilt

1
実際、 json_encodeを使用する必要がない$this->assertSame($exp, $arr); ため、類似の比較$this->assertEquals(json_encode($exp), json_encode($arr));を行うものを使用することもできます
maxwells

15

単純なヘルパーメソッド

protected function assertEqualsArrays($expected, $actual, $message) {
    $this->assertTrue(count($expected) == count(array_intersect($expected, $actual)), $message);
}

または、配列が等しくないときにさらにデバッグ情報が必要な場合

protected function assertEqualsArrays($expected, $actual, $message) {
    sort($expected);
    sort($actual);

    $this->assertEquals($expected, $actual, $message);
}

8

配列がソート可能な場合は、等価性をチェックする前に両方をソートします。そうでなければ、私はそれらをある種のセットに変換し、それらを比較します。


6

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

または2つのアサート(読みやすい):

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

それは賢いです:)
クリスチャン

まさに私が探していたもの。シンプル。
Abdul Maye


5

テストでは次のラッパーメソッドを使用します。

/**
 * 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

キーは同じであるが順不同である場合、これで解決するはずです。

同じ順序でキーを取得し、結果を比較するだけです。

 /**
 * 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);
}

3

多次元配列を処理できるようになり、2つの配列の違いを明確に伝えたいので、指定されたソリューションではうまくいきませんでした。

これが私の機能です

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)."`.");
            }
        }
    }
}

それを使用する

$this->assertArrayEquals($array1, $array2, array("/"));

1

まず、多次元配列からすべてのキーを取得する簡単なコードをいくつか作成しました。

 /**
 * 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;
}

次に、キーの順序に関係なく、それらが同じ構造になっていることをテストします。

    $expectedKeys = $this->getAllArrayKeys($expectedData);
    $actualKeys = $this->getAllArrayKeys($actualData);
    $this->assertEmpty(array_diff($expectedKeys, $actualKeys));

HTH


0

値がintまたは文字列のみで、複数レベルの配列がない場合...

配列を並べ替えるだけでなく、文字列に変換してください...

    $mapping = implode(',', array_sort($myArray));

    $list = implode(',', array_sort($myExpectedArray));

...そして次に文字列を比較します:

    $this->assertEquals($myExpectedArray, $myArray);

-2

配列の値のみをテストする場合は、次のことを実行できます。

$this->assertEquals(array_values($arrayOne), array_values($arrayTwo));

1
残念ながら、これは「値のみ」ではなく、値と値の順序の両方をテストしています。例えばecho("<pre>"); print_r(array_values(array("size" => "XL", "color" => "gold"))); print_r(array_values(array("color" => "gold", "size" => "XL")));
Pocketsand

-3

別のオプションは、あなたがまだ十分に持っていなかったかのように、結合することであるassertArraySubsetと組み合わせてassertCount、あなたの主張を作ること。したがって、コードは次のようになります。

self::assertCount(EXPECTED_NUM_ELEMENT, $array); self::assertArraySubset(SUBSET, $array);

この方法では、注文に依存しませんが、すべての要素が存在していると主張します。


assertArraySubset、それは動作しませんので、インデックスの順序は問題。つまり、self :: assertArraySubset(['a']、['b'、 'a'])はfalseになります。これ[0 => 'a']は、内部にないためです[0 => 'b', 1 => 'a']
Robert T.

申し訳ありませんが、ロバートと同意する必要があります。最初は、これが文字列キーと配列を比較するための良い解決策になると思いましたがassertEquals、キーが同じ順序でない場合はすでに処理しています。私はそれをテストしました。
Kodos Johnson 2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.