PHPUnit:断言两个数组相等,但元素的顺序并不重要

发布于 2024-09-25 08:45:10 字数 47 浏览 7 评论 0原文

当数组中元素的顺序不重要甚至可能发生变化时,断言两个对象数组相等的好方法是什么?

What is a good way to assert that two arrays of objects are equal, when the order of the elements in the array is unimportant, or even subject to change?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(18

娇俏 2024-10-02 08:45:10

您可以使用 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

You can use assertEqualsCanonicalizing method which was added in PHPUnit 7.5. If you compare the arrays using this method, these arrays will be sorted by PHPUnit arrays comparator itself.

Code example:

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 older versions of PHPUnit you can use an undocumented param $canonicalize of assertEquals method. If you pass $canonicalize = true, you will get the same effect:

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 source code at latest version of PHPUnit: https://github.com/sebastianbergmann/comparator/blob/master/src/ArrayComparator.php#L46

成熟的代价 2024-10-02 08:45:10

我的问题是我有 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));

My problem was that I had 2 arrays (array keys are not relevant for me, just the values).

For example I wanted to test if

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

had the same content (order not relevant for me) as

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

So I have used array_diff.

Final result was (if the arrays are equal, the difference will result in an empty array). Please note that the difference is computed both ways (Thanks @beret, @GordonM)

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

For a more detailed error message (while debugging), you can also test like this (thanks @DenilsonSá):

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

Old version with bugs inside:

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

盛夏尉蓝 2024-10-02 08:45:10

最简洁的方法是使用新的断言方法来扩展 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));

The cleanest way to do this would be to extend phpunit with a new assertion method. But here's an idea for a simpler way for now. Untested code, please verify:

Somewhere in your 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 your test:

$this->assertTrue(arrays_are_similar($foo, $bar));
笨死的猪 2024-10-02 08:45:10

另一种可能性:

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

One other possibility:

  1. Sort both arrays
  2. Convert them to a string
  3. Assert both strings are equal

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

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

$this->assertEquals(json_encode($exp), json_encode($arr));
不奢求什么 2024-10-02 08:45:10

简单的辅助方法

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

或者如果您在数组不相等时需要更多调试信息

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

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

Simple helper method

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

Or if you need more debug info when arrays are not equal

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

    $this->assertEquals(sort($expected), sort($actual), $message);
}
滥情空心 2024-10-02 08:45:10

如果密钥相同但顺序混乱,这应该可以解决问题。

您只需按相同顺序获取密钥并比较结果即可。

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

If the keys are the same but out of order this should solve it.

You just have to get the keys in the same order and compare the results.

 /**
 * 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);
}
花桑 2024-10-02 08:45:10

即使您不关心顺序,考虑到这一点可能会更容易:

尝试:

asort($foo);
asort($bar);
$this->assertEquals($foo, $bar);

Even though you do not care about the order, it might be easier to take that into account:

Try:

asort($foo);
asort($bar);
$this->assertEquals($foo, $bar);
趁年轻赶紧闹 2024-10-02 08:45:10

如果数组是可排序的,我会在检查相等性之前对它们进行排序。如果没有,我会将它们转换为某种类型的集合并进行比较。

If the array is sortable, I would sort them both before checking equality. If not, I would convert them to sets of some sort and compare those.

揪着可爱 2024-10-02 08:45:10

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

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

Or with 2 asserts (easier to read):

// 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)));
把时间冻结 2024-10-02 08:45:10

我们在测试中使用以下包装方法:

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

We use the following wrapper method in our Tests:

/**
 * 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);
}
半世晨晓 2024-10-02 08:45:10

给定的解决方案并不适合我,因为我希望能够处理多维数组并清楚地了解两个数组之间的差异。

这是我的函数

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

The given solutions didn't do the job for me because I wanted to be able to handle multi-dimensional array and to have a clear message of what is different between the two arrays.

Here is my function

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

Then to use it

$this->assertArrayEquals($array1, $array2, array("/"));
听,心雨的声音 2024-10-02 08:45:10

我编写了一些简单的代码,首先从多维数组中获取所有键:

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

I wrote some simple code to first get all the keys from a multi-dimensional array:

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

Then to test that they were structured the same regardless of the order of keys:

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

HTH

最初的梦 2024-10-02 08:45:10

assertEqualsCanonicalizing() 的问题是,如果您要比较具有更复杂结构和更大深度的数组,则不能依赖它。

最好的方法是使用 assertJsonStringEqualsJsonString 函数:

$this->assertJsonStringEqualsJsonString(
    json_encode($arr1), // expected
    json_encode($arr2), // actual
);

优点:

  1. 它是
  2. PHPUnit 的一部分(docs)
  3. 可以使用任何深度
  4. 键的顺序并不重要
  5. 如果有不匹配的情况,则会显示差异(PhpStorm)

免得看到详细解释:

public function test_example(): void
{
    // changing keys order -> OK
    $this->assertJsonStringEqualsJsonString(
        expectedJson: json_encode(['name' => 'Lukas', 'age' => 30]),
        actualJson: json_encode(['age' => 30, 'name' => 'Lukas']),
    );

    // prepare data with greater depth
    $expectedData = [
        'name' => 'Lukas',
        'age' => 30,
        'relatives' => [
            'spouse' => ['name' => 'Mary', 'age' => 29],
            'siblings' => [
                ['name' => 'Adam', 'age' => 33],
                ['name' => 'Kasia', 'age' => 24],
            ],
        ],
    ];

    // let's swap the keys, but without changing the meaning
    $actualData = [
        'relatives' => [
            'siblings' => [
                ['age' => 33, 'name' => 'Adam'],
                ['age' => 24, 'name' => 'Kasia'],
            ],
            'spouse' => ['age' => 29, 'name' => 'Mary'],
        ],
        'age' => 30,
        'name' => 'Lukas',
    ];

    // nested comparasion -> OK
    $this->assertJsonStringEqualsJsonString(
        expectedJson: json_encode($expectedData),
        actualJson: json_encode($actualData),
    );

    // is should be OK, but failed
    // because `assertEqualsCanonicalizing` can't handle arrays of greater depth
    //
    // $this->assertEqualsCanonicalizing(
    //     expected: $expectedData,
    //     actual: $actualData,
    // );

    // and lastly lets check IDE difference
    $actualData['name'] = 'Peter Parker';
    $actualData['relatives']['spouse']['name'] = 'Mary Jane';
    $this->assertJsonStringEqualsJsonString(
        expectedJson: json_encode($expectedData),
        actualJson: json_encode($actualData),
    );
}

在PhpStorm中查看差异的示例:

PhpStorm 比较失败

The problem with assertEqualsCanonicalizing() is that it can't be relied upon if you're comparing arrays with more complex structures and greater depth.

The best approach is to use the assertJsonStringEqualsJsonString function:

$this->assertJsonStringEqualsJsonString(
    json_encode($arr1), // expected
    json_encode($arr2), // actual
);

Benefits:

  1. Its clean
  2. Its part of PHPUnit (docs)
  3. Any depth can be used
  4. The order of the keys does not matter
  5. If there is a mismatch, it will show the difference (PhpStorm)

Lest see detailed explanation:

public function test_example(): void
{
    // changing keys order -> OK
    $this->assertJsonStringEqualsJsonString(
        expectedJson: json_encode(['name' => 'Lukas', 'age' => 30]),
        actualJson: json_encode(['age' => 30, 'name' => 'Lukas']),
    );

    // prepare data with greater depth
    $expectedData = [
        'name' => 'Lukas',
        'age' => 30,
        'relatives' => [
            'spouse' => ['name' => 'Mary', 'age' => 29],
            'siblings' => [
                ['name' => 'Adam', 'age' => 33],
                ['name' => 'Kasia', 'age' => 24],
            ],
        ],
    ];

    // let's swap the keys, but without changing the meaning
    $actualData = [
        'relatives' => [
            'siblings' => [
                ['age' => 33, 'name' => 'Adam'],
                ['age' => 24, 'name' => 'Kasia'],
            ],
            'spouse' => ['age' => 29, 'name' => 'Mary'],
        ],
        'age' => 30,
        'name' => 'Lukas',
    ];

    // nested comparasion -> OK
    $this->assertJsonStringEqualsJsonString(
        expectedJson: json_encode($expectedData),
        actualJson: json_encode($actualData),
    );

    // is should be OK, but failed
    // because `assertEqualsCanonicalizing` can't handle arrays of greater depth
    //
    // $this->assertEqualsCanonicalizing(
    //     expected: $expectedData,
    //     actual: $actualData,
    // );

    // and lastly lets check IDE difference
    $actualData['name'] = 'Peter Parker';
    $actualData['relatives']['spouse']['name'] = 'Mary Jane';
    $this->assertJsonStringEqualsJsonString(
        expectedJson: json_encode($expectedData),
        actualJson: json_encode($actualData),
    );
}

An example of viewing the difference in PhpStorm:

PhpStorm Comparison Failure

七色彩虹 2024-10-02 08:45:10
$this->assertEquals(json_encode($expectedData), json_encode($data), 'data should match');
$this->assertEquals(json_encode($expectedData), json_encode($data), 'data should match');
人生戏 2024-10-02 08:45:10

我正在转换为 json 并使用 Laravel assertJson():

$this->assertJson(json_encode($arrayOne), json_encode($arrayTwo));

I am converting to json and using Laravel assertJson():

$this->assertJson(json_encode($arrayOne), json_encode($arrayTwo));
单身狗的梦 2024-10-02 08:45:10

如果值只是 int 或字符串,并且没有多级数组......

为什么不直接对数组进行排序,将它们转换为字符串......

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

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

然后比较字符串:

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

If values are just int or strings, and no multiple level arrays....

Why not just sorting the arrays, convert them to string...

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

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

... and then compare string:

    $this->assertEquals($myExpectedArray, $myArray);
情丝乱 2024-10-02 08:45:10

如果您只想测试数组的值,您可以执行以下操作:

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

If you want test only the values of the array you can do:

$this->assertEquals(array_values($arrayOne), array_values($arrayTwo));
秋日私语 2024-10-02 08:45:10

另一种选择(就好像您还没有足够的一样)是将 assertArraySubsetassertCount 结合起来进行断言。所以,你的代码看起来像这样。

<代码>
self::assertCount(EXPECTED_NUM_ELEMENT, $array);
self::assertArraySubset(SUBSET, $array);

这样,您就可以独立于顺序,但仍然断言所有元素都存在。

Another option, as if you didn't already have enough, is to combine assertArraySubset combined with assertCount to make your assertion. So, your code would look something like.


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

This way you are order independent but still assert that all your elements are present.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文