PHPUnit: khẳng định hai mảng bằng nhau, nhưng thứ tự các phần tử không quan trọng


132

Một cách tốt để khẳng định rằng hai mảng đối tượng là bằng nhau, khi thứ tự của các phần tử trong mảng là không quan trọng, hoặc thậm chí có thể thay đổi?


Bạn có quan tâm đến các đối tượng trong mảng ong bằng nhau không hay chỉ có x lượng đối tượng y trong cả hai mảng?
edorian

@edorian Cả hai sẽ thú vị nhất. Trong trường hợp của tôi mặc dù chỉ có một đối tượng y trong mỗi mảng.
koen

hãy xác định bằng nhau . Là so sánh băm đối tượng được sắp xếp những gì bạn cần? Có lẽ bạn sẽ phải sắp xếp các đối tượng .
takeshin

@takeshin Bằng như trong ==. Trong trường hợp của tôi, chúng là các đối tượng giá trị nên không cần thiết. Tôi có thể có thể tạo ra một phương pháp khẳng định tùy chỉnh. Những gì tôi cần trong đó là đếm số lượng phần tử trong mỗi mảng và cho mỗi phần tử trong cả hai trên bằng nhau (==) phải tồn tại.
koen

7
Trên thực tế, trên PHPUnit 3.7.24, $ this-> assertEquals khẳng định mảng chứa các khóa và giá trị giống nhau, không quan tâm đến thứ tự nào.
Dereckson

Câu trả lời:


38

Cách sạch nhất để làm điều này là mở rộng phpunit bằng một phương thức khẳng định mới. Nhưng đây là một ý tưởng cho một cách đơn giản hơn bây giờ. Mã chưa được kiểm tra, vui lòng xác minh:

Một nơi nào đó trong ứng dụng của bạn:

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

Trong bài kiểm tra của bạn:

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

Craig, bạn gần với những gì tôi đã thử ban đầu. Trên thực tế Array_diff là những gì tôi cần, nhưng nó dường như không hoạt động cho các đối tượng. Tôi đã viết xác nhận tùy chỉnh của mình như được giải thích ở đây: phpunit.de/manual/civerse/en/extending-phastait.html
koen

Liên kết chính xác bây giờ là với https và không có www: phpunit.de/manual/c Hiện / en / extending
Xavi Montero

phần foreach là không cần thiết - Array_diff_assoc đã so sánh cả khóa và giá trị. EDIT: và bạn cũng cần kiểm tra count(array_diff_assoc($b, $a)).
JohnSmith

212

Bạn có thể dùng phương thức assertEqualsCanonicalizing đã được thêm vào trong PHPUnit 7.5. Nếu bạn so sánh các mảng bằng phương thức này, các mảng này sẽ được sắp xếp theo chính bộ so sánh mảng PHPUnit.

Mã ví dụ:

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

Trong các phiên bản cũ hơn của PHPUnit, bạn có thể sử dụng một tham số chuẩn hóa $ paramicalize của phương thức assertEquals . Nếu bạn vượt qua $ canonicalize = true , bạn sẽ nhận được hiệu ứng tương tự:

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

Sắp xếp mã nguồn so sánh ở phiên bản mới nhất của PHPUnit: https://github.com/sebastianbergmann/comparator/blob/master/src/ArrayComparator.php#L46


10
Tuyệt diệu. Tại sao đây không phải là câu trả lời được chấp nhận, @koen?
rinogo

7
Việc sử dụng $delta = 0.0, $maxDepth = 10, $canonicalize = trueđể truyền tham số vào hàm là sai lệch - PHP không hỗ trợ các đối số được đặt tên. Điều này thực sự đang làm là thiết lập ba biến đó, sau đó ngay lập tức chuyển các giá trị của chúng cho hàm. Điều này sẽ gây ra vấn đề nếu ba biến đó đã được xác định trong phạm vi cục bộ vì chúng sẽ bị ghi đè.
Yi Jiang

11
@ yi-jiang, đó chỉ là cách ngắn nhất để giải thích ý nghĩa của các đối số bổ sung. Đó là tự mô tả nhiều hơn sau đó biến thể sạch hơn : $this->assertEquals($array1, $array2, "\$canonicalize = true", 0.0, 10, true);. Tôi có thể sử dụng 4 dòng thay vì 1, nhưng tôi đã không làm điều đó.
pryazhnikov

8
Bạn không chỉ ra rằng giải pháp này sẽ loại bỏ các phím.
Odalrick

8
lưu ý rằng $canonicalizesẽ bị xóa: github.com/sebastianbergmann/phastait/issues/3342assertEqualsCanonicalizing()sẽ thay thế nó.
koen

35

Vấn đề của tôi là tôi đã có 2 mảng (các khóa mảng không liên quan đến tôi, chỉ là các giá trị).

Ví dụ tôi muốn kiểm tra nếu

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

có cùng nội dung (thứ tự không liên quan đến tôi) như

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

Vì vậy, tôi đã sử dụng mảng_diff .

Kết quả cuối cùng là (nếu các mảng bằng nhau, sự khác biệt sẽ dẫn đến một mảng trống). Xin lưu ý rằng sự khác biệt được tính theo cả hai cách (Cảm ơn @beret, @GordonM)

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

Đối với thông báo lỗi chi tiết hơn (trong khi gỡ lỗi), bạn cũng có thể kiểm tra như thế này (cảm ơn @ DenilsonSá):

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

Phiên bản cũ có lỗi bên trong:

$ this-> assertEmpty (mảng_diff ($ mảng2, $ mảng1));


Vấn đề của cách tiếp cận này là nếu $array1có nhiều giá trị hơn $array2, thì nó trả về mảng trống mặc dù giá trị mảng không bằng nhau. Bạn cũng nên kiểm tra, kích thước mảng là như nhau, để chắc chắn.
petrkotek

3
Bạn nên thực hiện cả Array_diff hoặc Array_diff_assoc theo cả hai cách. Nếu một mảng là siêu khối của mảng khác thì mảng_diff theo một hướng sẽ trống, nhưng không trống ở hướng khác. $a1 = [1,2,3,4,5]; $a2 = [1,3,5]; var_dump (array_diff ($a1, $a2)); var_dump (array_diff ($a2, $a1))
GordonM

2
assertEmptysẽ không in mảng nếu nó không trống, điều này gây bất tiện trong khi kiểm tra gỡ lỗi. Tôi khuyên bạn nên sử dụng : $this->assertSame(array_diff($expected, $actual), array_diff($actual, $expected), $message);, vì điều này sẽ in thông báo lỗi hữu ích nhất với tối thiểu mã bổ sung. Điều này hoạt động vì A \ B = B \ A A \ B và B \ A trống ⇔ A = B
Denilson Sá Maia

Lưu ý rằng Array_diff chuyển đổi mọi giá trị thành chuỗi để so sánh.
Konstantin Pelepelin

Để thêm vào @checat: bạn sẽ nhận được một Array to string conversionthông báo khi bạn cố gắng truyền một mảng thành một chuỗi. Một cách để khắc phục điều này là bằng cách sử dụngimplode
ub3rst4r

20

Một khả năng khác:

  1. Sắp xếp cả hai mảng
  2. Chuyển đổi chúng thành một chuỗi
  3. Khẳng định cả hai chuỗi bằng nhau

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

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

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

Nếu một trong hai mảng chứa các đối tượng, json_encode chỉ mã hóa các thuộc tính công khai. Điều này sẽ vẫn hoạt động, nhưng chỉ khi tất cả các thuộc tính xác định sự bình đẳng là công khai. Hãy xem giao diện sau để kiểm soát json_encoding của các thuộc tính riêng tư. php.net/manual/en/class.jsonserializable.php
Westy92

1
Điều này hoạt động ngay cả khi không sắp xếp. Đối với assertEqualsthứ tự không quan trọng.
Héo

1
Thật vậy, chúng ta cũng có thể sử dụng $this->assertSame($exp, $arr); phép so sánh tương tự vì $this->assertEquals(json_encode($exp), json_encode($arr)); chỉ khác là chúng ta không phải sử dụng json_encode
maxwell

15

Phương pháp trợ giúp đơn giản

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

Hoặc nếu bạn cần thêm thông tin gỡ lỗi khi mảng không bằng nhau

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

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

8

Nếu mảng có thể sắp xếp, tôi sẽ sắp xếp cả hai trước khi kiểm tra đẳng thức. Nếu không, tôi sẽ chuyển đổi chúng thành các bộ sắp xếp và so sánh chúng.


6

Sử dụng mảng_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)));

Hoặc với 2 khẳng định (dễ đọc hơn):

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

Thật thông minh :)
Christian

Chính xác những gì tôi đang tìm kiếm. Đơn giản.
Abdul Maye

6

Mặc dù bạn không quan tâm đến đơn hàng, nhưng có thể dễ dàng hơn khi tính đến điều đó:

Thử:

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

5

Chúng tôi sử dụng phương pháp trình bao bọc sau trong các Thử nghiệm của mình:

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

Nếu các khóa là như nhau nhưng không theo thứ tự này nên giải quyết nó.

Bạn chỉ cần lấy các khóa theo cùng một thứ tự và so sánh kết quả.

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

Các giải pháp đã cho không làm được việc cho tôi vì tôi muốn có thể xử lý mảng đa chiều và có một thông điệp rõ ràng về sự khác biệt giữa hai mảng.

Đây là chức năng của tôi

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

Sau đó sử dụng nó

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

1

Tôi đã viết một số mã đơn giản để đầu tiên nhận được tất cả các khóa từ một mảng đa chiều:

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

Sau đó, để kiểm tra rằng chúng có cấu trúc giống nhau bất kể thứ tự các khóa:

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

HTH


0

Nếu các giá trị chỉ là int hoặc chuỗi và không có nhiều cấp độ ....

Tại sao không chỉ sắp xếp các mảng, chuyển đổi chúng thành chuỗi ...

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

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

... và sau đó so sánh chuỗi:

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

-2

Nếu bạn chỉ muốn kiểm tra các giá trị của mảng, bạn có thể làm:

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

1
Thật không may, đó không phải là thử nghiệm "chỉ các giá trị" mà cả các giá trị và thứ tự của các giá trị. Ví dụ:echo("<pre>"); print_r(array_values(array("size" => "XL", "color" => "gold"))); print_r(array_values(array("color" => "gold", "size" => "XL")));
Pocketsand

-3

Một lựa chọn khác, như thể bạn chưa có đủ, là kết hợp kết assertArraySubsethợp với assertCountđể đưa ra khẳng định của bạn. Vì vậy, mã của bạn sẽ trông giống như.

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

Bằng cách này, bạn độc lập trật tự nhưng vẫn khẳng định rằng tất cả các yếu tố của bạn đều có mặt.


Theo assertArraySubsetthứ tự các chỉ số quan trọng vì vậy nó sẽ không hoạt động. tức là self :: assertArraySubset (['a'], ['b', 'a']) sẽ sai, vì [0 => 'a']không ở bên trong[0 => 'b', 1 => 'a']
Robert T.

Xin lỗi nhưng tôi phải đồng ý với Robert. Lúc đầu, tôi nghĩ rằng đây sẽ là một giải pháp tốt để so sánh các mảng với các khóa chuỗi, nhưng assertEqualsđã xử lý nếu các khóa không theo cùng một thứ tự. Tôi chỉ thử nó.
Kodos Johnson
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.