PHPUnit: ยืนยันว่าอาร์เรย์สองอาร์เรย์เท่ากัน แต่ลำดับขององค์ประกอบไม่สำคัญ


132

อะไรคือวิธีที่ดีในการยืนยันว่าอาร์เรย์ของออบเจ็กต์สองอาร์เรย์เท่ากันเมื่อลำดับขององค์ประกอบในอาร์เรย์ไม่สำคัญหรือแม้กระทั่งอาจมีการเปลี่ยนแปลง


คุณสนใจเกี่ยวกับวัตถุในอาร์เรย์ที่มีค่าเท่ากันหรือเพียงแค่ว่ามีจำนวน x ของวัตถุ y ในทั้งสองอาร์เรย์
edorian

@edorian ทั้งคู่น่าสนใจที่สุด ในกรณีของฉันแม้ว่าจะมีวัตถุ y เพียงตัวเดียวในแต่ละอาร์เรย์
koen

กรุณากำหนดเท่ากับ การเปรียบเทียบแฮชวัตถุที่จัดเรียงคุณต้องการอะไร? คุณอาจต้องเรียงลำดับวัตถุอยู่ดี
takeshin

@takeshin เท่ากับใน == ในกรณีของฉันมันเป็นวัตถุที่มีค่าดังนั้นความเหมือนจึงไม่จำเป็น ฉันอาจสร้างวิธีการยืนยันแบบกำหนดเองได้ สิ่งที่ฉันต้องการในนั้นคือนับจำนวนองค์ประกอบในแต่ละอาร์เรย์และสำหรับแต่ละองค์ประกอบในทั้งสองเท่ากัน (==) จะต้องมีอยู่
koen

7
ที่จริงแล้วบน PHPUnit 3.7.24 $ this-> assertEquals จะยืนยันว่าอาร์เรย์มีคีย์และค่าเดียวกันโดยไม่สนใจว่าจะเรียงลำดับอย่างไร
Dereckson

คำตอบ:


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

ส่วนหน้าไม่จำเป็น - array_diff_assoc เปรียบเทียบทั้งคีย์และค่าแล้ว แก้ไข: และคุณต้องตรวจสอบcount(array_diff_assoc($b, $a))ด้วย
JohnSmith

212

คุณสามารถใช้เมธอดassertEqualsCanonicalizingซึ่งเพิ่มใน PHPUnit 7.5 หากคุณเปรียบเทียบอาร์เรย์โดยใช้วิธีนี้อาร์เรย์เหล่านี้จะถูกจัดเรียงโดยตัวเปรียบเทียบอาร์เรย์ 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 ที่ไม่มีเอกสาร param $ 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

7
การใช้$delta = 0.0, $maxDepth = 10, $canonicalize = trueเพื่อส่งผ่านพารามิเตอร์ไปยังฟังก์ชันทำให้เข้าใจผิด - PHP ไม่รองรับอาร์กิวเมนต์ที่ตั้งชื่อ สิ่งที่ทำคือการตั้งค่าตัวแปรทั้งสามแล้วส่งผ่านค่าไปยังฟังก์ชันทันที สิ่งนี้จะทำให้เกิดปัญหาหากตัวแปรทั้งสามถูกกำหนดไว้แล้วในขอบเขตโลคัลเนื่องจากจะถูกเขียนทับ
Yi Jiang

11
@ yi-jiang เป็นเพียงวิธีที่สั้นที่สุดในการอธิบายความหมายของข้อโต้แย้งเพิ่มเติม $this->assertEquals($array1, $array2, "\$canonicalize = true", 0.0, 10, true);มันขึ้นบรรยายตัวเองแล้วตัวแปรสะอาดเพิ่มเติมได้ที่: ฉันใช้ 4 บรรทัดแทน 1 ได้ แต่ฉันไม่ได้ทำอย่างนั้น
pryazhnikov

8
คุณไม่ได้ชี้ให้เห็นว่าโซลูชันนี้จะทิ้งคีย์
Odalrick

8
โปรดทราบว่า$canonicalizeจะถูกลบ: github.com/sebastianbergmann/phpunit/issues/3342และassertEqualsCanonicalizing()จะแทนที่
koen

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

3
คุณควรทำ array_diff หรือ array_diff_assoc ทั้งสองวิธี ถ้าอาร์เรย์หนึ่งเป็นชุดซูเปอร์เซ็ตของอีกอาร์เรย์อาร์เรย์ในทิศทางหนึ่งจะว่างเปล่า แต่อีกด้านหนึ่งไม่ว่างเปล่า $a1 = [1,2,3,4,5]; $a2 = [1,3,5]; var_dump (array_diff ($a1, $a2)); var_dump (array_diff ($a2, $a1))
GordonM

2
assertEmptyจะไม่พิมพ์อาร์เรย์หากไม่ว่างเปล่าซึ่งไม่สะดวกขณะทำการทดสอบการดีบัก ฉันขอแนะนำให้ใช้: $this->assertSame(array_diff($expected, $actual), array_diff($actual, $expected), $message);เนื่องจากจะพิมพ์ข้อความแสดงข้อผิดพลาดที่เป็นประโยชน์ที่สุดโดยมีรหัสพิเศษขั้นต่ำ ใช้งานได้เพราะA \ B = B \ A ⇔ A \ B และ B \ A ว่างเปล่า⇔ A = B
Denilson Sá Maia

โปรดสังเกตว่า array_diff แปลงทุกค่าเป็นสตริงเพื่อเปรียบเทียบ
Konstantin Pelepelin

หากต้องการเพิ่มใน @checat: คุณจะได้รับArray to string conversionข้อความเมื่อคุณพยายามส่งอาร์เรย์ไปยังสตริง วิธีแก้ปัญหานี้คือใช้implode
ub3rst4r

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การสั่งซื้อไม่สำคัญ
ร่วง

1
อันที่จริงเราสามารถใช้$this->assertSame($exp, $arr); ซึ่งทำการเปรียบเทียบที่คล้ายกันได้เช่น$this->assertEquals(json_encode($exp), json_encode($arr)); กันความแตกต่างเพียงอย่างเดียวคือเราไม่ต้องใช้ json_encode
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)));

ฉลาดมาก :)
Christian

สิ่งที่ฉันกำลังมองหา ง่าย
Abdul Maye

6

แม้ว่าคุณจะไม่สนใจคำสั่งซื้อ แต่ก็อาจพิจารณาได้ง่ายกว่า:

ลอง:

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

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

โซลูชันที่ระบุไม่ได้ผลสำหรับฉันเพราะฉันต้องการจัดการอาร์เรย์หลายมิติและมีข้อความที่ชัดเจนว่าอะไรแตกต่างกันระหว่างอาร์เรย์ทั้งสอง

นี่คือหน้าที่ของฉัน

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")));
กระเป๋าและ

-3

อีกทางเลือกหนึ่งเช่นถ้าคุณมีไม่เพียงพอก็คือการรวมassertArraySubsetเข้าด้วยกันassertCountเพื่อยืนยันตัวตนของคุณ ดังนั้นรหัสของคุณจะมีลักษณะดังนี้

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

วิธีนี้ทำให้คุณเป็นอิสระ แต่ยังคงยืนยันว่าองค์ประกอบทั้งหมดของคุณมีอยู่


ในassertArraySubsetคำสั่งของดัชนีเรื่องดังนั้นมันจะไม่ทำงาน เช่น self :: assertArraySubset (['a'], ['b', 'a']) จะเป็นเท็จเพราะ[0 => 'a']ไม่ได้อยู่ข้างใน[0 => 'b', 1 => 'a']
Robert T.

ขอโทษทีฉันต้องเห็นด้วยกับโรเบิร์ต ตอนแรกฉันคิดว่านี่จะเป็นทางออกที่ดีในการเปรียบเทียบอาร์เรย์กับคีย์สตริง แต่assertEqualsจัดการได้แล้วว่าถ้าคีย์ไม่เรียงลำดับกัน ฉันเพิ่งทดสอบมัน
Kodos Johnson
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.