PHPUnit: menyatakan dua array sama, tetapi urutan elemen tidak penting


132

Apa cara yang baik untuk menyatakan bahwa dua array objek adalah sama, ketika urutan elemen-elemen dalam array tidak penting, atau bahkan dapat berubah?


Apakah Anda peduli tentang objek dalam array beeing sama atau hanya bahwa ada jumlah x objek y di kedua array?
edorian

@edorian Keduanya akan sangat menarik. Dalam kasus saya, meskipun hanya ada satu objek y di setiap array.
koen

tolong sebutkan sama . Apakah membandingkan hash objek yang diurutkan apa yang Anda butuhkan? Anda mungkin harus menyortir objek .
takeshin

@takeshin Sama dengan di ==. Dalam kasus saya mereka adalah objek nilai sehingga kesamaan tidak diperlukan. Saya mungkin bisa membuat metode pernyataan kustom. Apa yang saya perlukan di dalamnya adalah menghitung jumlah elemen dalam setiap array, dan untuk setiap elemen dalam keduanya sama dengan (==) harus ada.
koen

7
Sebenarnya, pada PHPUnit 3.7.24, $ this-> assertEquals menegaskan array berisi kunci dan nilai yang sama, mengabaikan urutan apa.
Dereckson

Jawaban:


38

Cara terbersih untuk melakukan ini adalah memperluas phpunit dengan metode penegasan baru. Tapi ini ide untuk cara yang lebih sederhana untuk saat ini. Kode belum diuji, harap verifikasi:

Di suatu tempat di aplikasi Anda:

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

Dalam tes Anda:

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

Craig, Anda dekat dengan apa yang saya coba pada awalnya. Sebenarnya array_diff adalah apa yang saya butuhkan, tetapi sepertinya tidak berfungsi untuk objek. Saya memang menulis pernyataan kustom saya seperti yang dijelaskan di sini: phpunit.de/manual/current/en/extending-phpunit.html
koen

Tautan yang tepat sekarang adalah dengan https dan tanpa www: phpunit.de/manual/current/en/extending-phpunit.html
Xavi Montero

bagian foreach tidak diperlukan - array_diff_assoc sudah membandingkan kunci dan nilai. Sunting: dan Anda perlu memeriksa count(array_diff_assoc($b, $a))juga.
JohnSmith

212

Anda dapat menggunakan metode assertEqualsCanonicalize yang ditambahkan di PHPUnit 7.5. Jika Anda membandingkan array menggunakan metode ini, array ini akan diurutkan oleh komparator array PHPUnit itu sendiri.

Contoh kode:

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

Dalam versi PHPUnit yang lebih lama, Anda dapat menggunakan param $ canonicalize dari metode assertEquals tanpa dokumen . Jika Anda melewatkan $ canonicalize = true , Anda akan mendapatkan efek yang sama:

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

Array kode sumber komparator pada versi terbaru dari PHPUnit: https://github.com/sebastianbergmann/comparator/blob/master/src/ArrayComparator.php#L46


10
Fantastis. Mengapa ini bukan jawaban yang diterima, @ Koen?
rinogo

7
Menggunakan $delta = 0.0, $maxDepth = 10, $canonicalize = trueuntuk melewatkan parameter ke dalam fungsi menyesatkan - PHP tidak mendukung argumen bernama. Apa yang sebenarnya dilakukan adalah mengatur ketiga variabel tersebut, lalu segera meneruskan nilainya ke fungsi. Ini akan menimbulkan masalah jika ketiga variabel tersebut sudah didefinisikan dalam lingkup lokal karena mereka akan ditimpa.
Yi Jiang

11
@ yi-jiang, itu hanya cara terpendek untuk menjelaskan arti argumen tambahan. Ini lebih self-deskriptif varian kemudian lebih bersih: $this->assertEquals($array1, $array2, "\$canonicalize = true", 0.0, 10, true);. Saya bisa menggunakan 4 baris, bukan 1, tapi saya tidak melakukannya.
pryazhnikov

8
Anda tidak menunjukkan bahwa solusi ini akan membuang kunci.
Odalrick

8
perhatikan bahwa $canonicalizeakan dihapus: github.com/sebastianbergmann/phpunit/issues/3342 dan assertEqualsCanonicalizing()akan menggantinya.
koen

35

Masalah saya adalah saya memiliki 2 array (kunci array tidak relevan untuk saya, hanya nilainya).

Misalnya saya ingin menguji apakah

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

memiliki konten yang sama (pesanan tidak relevan untuk saya) seperti

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

Jadi saya telah menggunakan array_diff .

Hasil akhir adalah (jika array sama, perbedaannya akan menghasilkan array kosong). Harap dicatat bahwa perbedaannya dihitung dua arah (Terima kasih @beret, @GordonM)

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

Untuk pesan kesalahan yang lebih terperinci (saat debugging), Anda juga dapat menguji seperti ini (terima kasih @ DenilsonSá):

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

Versi lama dengan bug di dalamnya:

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


Masalah pendekatan ini adalah bahwa jika $array1memiliki nilai lebih dari $array2, maka ia mengembalikan array kosong meskipun nilai array tidak sama. Anda juga harus menguji, bahwa ukuran array sama, untuk memastikan.
petrkotek

3
Anda harus melakukan array_diff atau array_diff_assoc dua arah. Jika satu array adalah superset dari yang lain maka array_diff di satu arah akan kosong, tetapi tidak kosong di yang lain. $a1 = [1,2,3,4,5]; $a2 = [1,3,5]; var_dump (array_diff ($a1, $a2)); var_dump (array_diff ($a2, $a1))
GordonM

2
assertEmptytidak akan mencetak array jika tidak kosong, yang merepotkan saat uji debugging. Saya sarankan menggunakan:, $this->assertSame(array_diff($expected, $actual), array_diff($actual, $expected), $message);karena ini akan mencetak pesan kesalahan yang paling berguna dengan minimum kode tambahan. Ini berfungsi karena A \ B = B \ A ⇔ A \ B dan B \ A kosong ⇔ A = B
Denilson Sá Maia

Perhatikan bahwa array_diff mengonversi setiap nilai menjadi string untuk perbandingan.
Konstantin Pelepelin

Untuk menambahkan ke @checat: Anda akan mendapatkan Array to string conversionpesan ketika Anda mencoba untuk melemparkan array ke string. Cara untuk menyiasatinya adalah dengan menggunakanimplode
ub3rst4r

20

Satu kemungkinan lain:

  1. Sortir kedua array
  2. Konversikan menjadi string
  3. Tegaskan kedua string sama

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

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

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

Jika salah satu array berisi objek, json_encode hanya mengkodekan properti publik. Ini masih akan berfungsi, tetapi hanya jika semua properti yang menentukan kesetaraan bersifat publik. Lihatlah antarmuka berikut untuk mengontrol json_encoding properti pribadi. php.net/manual/en/class.jsonserializable.php
Westy92

1
Ini berfungsi bahkan tanpa memilah. Untuk assertEqualspesanan tidak masalah.
Wilt

1
Memang, kita juga dapat menggunakan $this->assertSame($exp, $arr); yang melakukan perbandingan serupa karena $this->assertEquals(json_encode($exp), json_encode($arr)); hanya perbedaannya adalah kita tidak harus menggunakan json_encode
maxwells

15

Metode pembantu sederhana

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

Atau jika Anda memerlukan lebih banyak info debug ketika array tidak sama

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

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

8

Jika arraynya bisa diurutkan, saya akan mengurutkan keduanya sebelum memeriksa persamaan. Jika tidak, saya akan mengonversinya menjadi beberapa set dan membandingkannya.


6

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

Atau dengan 2 menegaskan (lebih mudah dibaca):

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

Itu cerdas :)
Christian

Persis apa yang saya cari. Sederhana.
Abdul Maye

6

Meskipun Anda tidak peduli dengan pesanan, mungkin lebih mudah untuk memperhitungkannya:

Mencoba:

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

5

Kami menggunakan metode pembungkus berikut dalam Tes kami:

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

Jika kuncinya sama tetapi rusak, ini harus menyelesaikannya.

Anda hanya perlu mendapatkan kunci dalam urutan yang sama dan membandingkan hasilnya.

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

Solusi yang diberikan tidak melakukan pekerjaan untuk saya karena saya ingin dapat menangani array multi-dimensi dan memiliki pesan yang jelas tentang apa yang berbeda antara dua array.

Inilah fungsi saya

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

Lalu untuk menggunakannya

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

1

Saya menulis beberapa kode sederhana untuk pertama mendapatkan semua kunci dari array multi-dimensi:

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

Kemudian untuk menguji bahwa mereka terstruktur sama terlepas dari urutan kunci:

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

HTH


0

Jika nilai hanya int atau string, dan tidak ada array level ganda ....

Mengapa tidak hanya menyortir array, mengubahnya menjadi string ...

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

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

... lalu bandingkan string:

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

-2

Jika Anda ingin menguji hanya nilai-nilai array yang dapat Anda lakukan:

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

1
Sayangnya itu bukan pengujian "hanya nilai" tetapi nilai dan urutan nilai. Misalnyaecho("<pre>"); print_r(array_values(array("size" => "XL", "color" => "gold"))); print_r(array_values(array("color" => "gold", "size" => "XL")));
Pocketsand

-3

Pilihan lain, seolah-olah Anda belum memiliki cukup, adalah untuk menggabungkan assertArraySubsetdikombinasikan dengan assertCountuntuk membuat pernyataan Anda. Jadi, kode Anda akan terlihat seperti itu.

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

Dengan cara ini Anda dapat memesan secara independen tetapi tetap menegaskan bahwa semua elemen Anda ada.


Dalam assertArraySubseturutan masalah indeks sehingga tidak akan berfungsi. yaitu diri :: assertArraySubset (['a'], ['b', 'a']) akan salah, karena [0 => 'a']tidak ada di dalam[0 => 'b', 1 => 'a']
Robert T.

Maaf tapi saya harus setuju dengan Robert. Pada awalnya saya berpikir bahwa ini akan menjadi solusi yang baik untuk membandingkan array dengan kunci string, tetapi assertEqualssudah mengatasinya jika kunci tidak dalam urutan yang sama. Saya baru saja mengujinya.
Kodos Johnson
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.