Apa cara yang baik untuk menyatakan bahwa dua array objek adalah sama, ketika urutan elemen-elemen dalam array tidak penting, atau bahkan dapat berubah?
Apa cara yang baik untuk menyatakan bahwa dua array objek adalah sama, ketika urutan elemen-elemen dalam array tidak penting, atau bahkan dapat berubah?
Jawaban:
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));
count(array_diff_assoc($b, $a))
juga.
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
$delta = 0.0, $maxDepth = 10, $canonicalize = true
untuk 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.
$this->assertEquals($array1, $array2, "\$canonicalize = true", 0.0, 10, true);
. Saya bisa menggunakan 4 baris, bukan 1, tapi saya tidak melakukannya.
$canonicalize
akan dihapus: github.com/sebastianbergmann/phpunit/issues/3342 dan assertEqualsCanonicalizing()
akan menggantinya.
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));
$array1
memiliki nilai lebih dari $array2
, maka ia mengembalikan array kosong meskipun nilai array tidak sama. Anda juga harus menguji, bahwa ukuran array sama, untuk memastikan.
$a1 = [1,2,3,4,5]; $a2 = [1,3,5]; var_dump (array_diff ($a1, $a2)); var_dump (array_diff ($a2, $a1))
assertEmpty
tidak 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
Array to string conversion
pesan ketika Anda mencoba untuk melemparkan array ke string. Cara untuk menyiasatinya adalah dengan menggunakanimplode
Satu kemungkinan lain:
$arr = array(23, 42, 108);
$exp = array(42, 23, 108);
sort($arr);
sort($exp);
$this->assertEquals(json_encode($exp), json_encode($arr));
assertEquals
pesanan tidak masalah.
$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
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);
}
Jika arraynya bisa diurutkan, saya akan mengurutkan keduanya sebelum memeriksa persamaan. Jika tidak, saya akan mengonversinya menjadi beberapa set dan membandingkannya.
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)));
Meskipun Anda tidak peduli dengan pesanan, mungkin lebih mudah untuk memperhitungkannya:
Mencoba:
asort($foo);
asort($bar);
$this->assertEquals($foo, $bar);
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);
}
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);
}
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("/"));
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
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);
Jika Anda ingin menguji hanya nilai-nilai array yang dapat Anda lakukan:
$this->assertEquals(array_values($arrayOne), array_values($arrayTwo));
echo("<pre>"); print_r(array_values(array("size" => "XL", "color" => "gold"))); print_r(array_values(array("color" => "gold", "size" => "XL")));
Pilihan lain, seolah-olah Anda belum memiliki cukup, adalah untuk menggabungkan assertArraySubset
dikombinasikan dengan assertCount
untuk 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.
assertArraySubset
urutan 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']
assertEquals
sudah mengatasinya jika kunci tidak dalam urutan yang sama. Saya baru saja mengujinya.