代码之家  ›  专栏  ›  技术社区  ›  koen

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

  •  114
  • koen  · 技术社区  · 14 年前

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

    15 回复  |  直到 5 年前
        1
  •  33
  •   Craig    14 年前

    最干净的方法是使用新的断言方法扩展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));
    
        2
  •  159
  •   pryazhnikov    5 年前

    你可以用 断言均等化 方法:在phpunit 7.5中加入。如果使用此方法比较数组,这些数组将由phpunit array comparator本身排序。

    代码示例:

    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中,可以使用未记录的参数$canonicalize of 断言等于 方法。如果你通过 $canonicalize=真 你会得到同样的效果:

    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

        3
  •  34
  •   Mickäel A.    10 年前

    我的问题是我有两个数组(数组键与我无关,只是值)。

    例如,我想测试

    $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->断言为空(数组diff($array2,$array1));

        4
  •  18
  •   rodrigo-silveira selftaught91    11 年前

    另一种可能性是:

    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));
    
        5
  •  14
  •   ksimka    12 年前

    简单助手方法

    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);
    }
    
        6
  •  7
  •   Rodney Gitzel    14 年前

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

        7
  •  6
  •   caligari    9 年前

    使用 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)));
    
        8
  •  5
  •   theintz    11 年前

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

    /**
     * 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);
    }
    
        9
  •  5
  •   Zanshin13    9 年前

    如果键是相同的,但顺序不对,这应该解决它。

    您只需按照相同的顺序获取键并比较结果即可。

     /**
     * 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);
    }
    
        10
  •  4
  •   Ruben Steins    8 年前

    尽管您不关心订单,但考虑到这一点可能更容易:

    尝试:

    asort($foo);
    asort($bar);
    $this->assertEquals($foo, $bar);
    
        11
  •  2
  •   moins52    9 年前

    给定的解决方案并没有为我完成这项工作,因为我希望能够处理多维数组,并清楚地了解两个数组之间的区别。

    这是我的职能

    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("/"));
    
        12
  •  1
  •   sturrockad    9 年前

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

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

    高温高压

        13
  •  0
  •   koalaok    6 年前

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

    为什么不把数组排序,把它们转换成字符串…

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

    …然后比较字符串:

        $this->assertEquals($myExpectedArray, $myArray);
    
        14
  •  -1
  •   Anderson Contreira    7 年前

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

    $this->assertEquals(array_values($arrayOne), array_values($arrayTwo));
    
        15
  •  -3
  •   Jonathan    7 年前

    另一个选择,好像你还没有足够的资金,就是合并 assertArraySubset 结合 assertCount 做出你的断言。所以,您的代码应该是这样的。

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

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