Jaki jest dobry sposób na stwierdzenie, że dwie tablice obiektów są równe, gdy kolejność elementów w tablicy jest nieistotna lub nawet podlega zmianie?
php
unit-testing
phpunit
assert
koen
źródło
źródło
Odpowiedzi:
Najprostszym sposobem na to byłoby rozszerzenie phpunita o nową metodę asercji. Ale teraz mam pomysł na prostszy sposób. Niesprawdzony kod, zweryfikuj:
Gdzieś w Twojej aplikacji:
/** * 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; }
W twoim teście:
$this->assertTrue(arrays_are_similar($foo, $bar));
źródło
count(array_diff_assoc($b, $a))
też sprawdzić .Możesz użyć metody assertEqualsCanonicalizing , która została dodana w PHPUnit 7.5. Jeśli porównasz tablice za pomocą tej metody, te tablice zostaną posortowane przez sam komparator tablic PHPUnit.
Przykład kodu:
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; } }
W starszych wersjach PHPUnit można użyć nieudokumentowanego parametru $ canonicalize metody assertEquals . Jeśli przekażesz $ canonicalize = true , uzyskasz ten sam efekt:
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; } }
Kod źródłowy komparatora tablic w najnowszej wersji PHPUnit: https://github.com/sebastianbergmann/comparator/blob/master/src/ArrayComparator.php#L46
źródło
$delta = 0.0, $maxDepth = 10, $canonicalize = true
do przekazywania parametrów do funkcji jest mylące - PHP nie obsługuje nazwanych argumentów. W rzeczywistości ustawia te trzy zmienne, a następnie natychmiast przekazuje ich wartości do funkcji. Spowoduje to problemy, jeśli te trzy zmienne są już zdefiniowane w zakresie lokalnym, ponieważ zostaną nadpisane.$this->assertEquals($array1, $array2, "\$canonicalize = true", 0.0, 10, true);
. Mogłem użyć 4 linii zamiast 1, ale tego nie zrobiłem.$canonicalize
zostanie usunięty: github.com/sebastianbergmann/phpunit/issues/3342 iassertEqualsCanonicalizing()
zastąpi go.Mój problem polegał na tym, że miałem 2 tablice (klucze tablic nie są dla mnie istotne, tylko wartości).
Na przykład chciałem sprawdzić, czy
$expected = array("0" => "green", "2" => "red", "5" => "blue", "9" => "pink");
miał taką samą treść (zamówienie nie jest dla mnie istotne) co
$actual = array("0" => "pink", "1" => "green", "3" => "yellow", "red", "blue");
Więc użyłem array_diff .
Ostateczny wynik to (jeśli tablice są równe, różnica spowoduje pustą tablicę). Należy pamiętać, że różnica jest obliczana w obie strony (dzięki @beret, @GordonM)
$this->assertEmpty(array_merge(array_diff($expected, $actual), array_diff($actual, $expected)));
Aby uzyskać bardziej szczegółowy komunikat o błędzie (podczas debugowania), możesz również przetestować w ten sposób (dzięki @ DenilsonSá):
$this->assertSame(array_diff($expected, $actual), array_diff($actual, $expected));
Stara wersja z błędami w środku:
$ this-> assertEmpty (array_diff ($ tablica2, $ tablica1));źródło
$array1
ma więcej wartości niż$array2
, zwraca pustą tablicę, mimo że wartości tablicy nie są równe. Dla pewności należy również sprawdzić, czy rozmiar tablicy jest taki sam.$a1 = [1,2,3,4,5]; $a2 = [1,3,5]; var_dump (array_diff ($a1, $a2)); var_dump (array_diff ($a2, $a1))
assertEmpty
nie wypisze tablicy, jeśli nie jest pusta, co jest niewygodne podczas debugowania testów. Sugerowałbym użycie:,$this->assertSame(array_diff($expected, $actual), array_diff($actual, $expected), $message);
ponieważ spowoduje to wydrukowanie najbardziej użytecznego komunikatu o błędzie z minimalną ilością dodatkowego kodu. To działa, ponieważ A \ B = B \ A ⇔ A \ B i B \ A są puste ⇔ A = BArray to string conversion
komunikat, gdy spróbujesz rzutować tablicę na łańcuch. Sposobem na obejście tego jest użycieimplode
Jeszcze jedna możliwość:
$arr = array(23, 42, 108); $exp = array(42, 23, 108); sort($arr); sort($exp); $this->assertEquals(json_encode($exp), json_encode($arr));
źródło
assertEquals
porządku nie ma znaczenia.$this->assertSame($exp, $arr);
co robi podobne porównanie, ponieważ$this->assertEquals(json_encode($exp), json_encode($arr));
jedyną różnicą jest to, że nie musimy używać json_encodeProsta metoda pomocnicza
protected function assertEqualsArrays($expected, $actual, $message) { $this->assertTrue(count($expected) == count(array_intersect($expected, $actual)), $message); }
Lub jeśli potrzebujesz więcej informacji do debugowania, gdy tablice nie są równe
protected function assertEqualsArrays($expected, $actual, $message) { sort($expected); sort($actual); $this->assertEquals($expected, $actual, $message); }
źródło
Jeśli tablica jest sortowalna, posortowałbym je obie przed sprawdzeniem równości. Jeśli nie, zamieniłbym je na jakieś zestawy i porównałbym je.
źródło
Korzystanie z 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)));
Lub z 2 potwierdzeniami (łatwiejsze do odczytania):
// 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)));
źródło
Nawet jeśli nie zależy Ci na zamówieniu, może być łatwiej wziąć to pod uwagę:
Próbować:
asort($foo); asort($bar); $this->assertEquals($foo, $bar);
źródło
W naszych testach używamy następującej metody pakowania:
/** * 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); }
źródło
Jeśli klucze są takie same, ale nie działają, powinno to rozwiązać problem.
Musisz tylko zdobyć klucze w tej samej kolejności i porównać wyniki.
/** * 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); }
źródło
Podane rozwiązania nie sprawdziły się za mnie, ponieważ chciałem być w stanie obsłużyć wielowymiarowe tablice i mieć jasny komunikat o tym, co różni się między dwiema tablicami.
Oto moja funkcja
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)."`."); } } } }
Potem go użyć
$this->assertArrayEquals($array1, $array2, array("/"));
źródło
Napisałem prosty kod, aby najpierw uzyskać wszystkie klucze z wielowymiarowej tablicy:
/** * 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; }
Następnie, aby sprawdzić, czy mają taką samą strukturę niezależnie od kolejności kluczy:
$expectedKeys = $this->getAllArrayKeys($expectedData); $actualKeys = $this->getAllArrayKeys($actualData); $this->assertEmpty(array_diff($expectedKeys, $actualKeys));
HTH
źródło
Jeśli wartości to tylko int lub stringi i nie ma tablic wielopoziomowych ....
Dlaczego nie posortować tablice, przekonwertować je na łańcuchy ...
$mapping = implode(',', array_sort($myArray)); $list = implode(',', array_sort($myExpectedArray));
... a następnie porównaj ciąg:
$this->assertEquals($myExpectedArray, $myArray);
źródło
Jeśli chcesz przetestować tylko wartości tablicy, możesz to zrobić:
$this->assertEquals(array_values($arrayOne), array_values($arrayTwo));
źródło
echo("<pre>"); print_r(array_values(array("size" => "XL", "color" => "gold"))); print_r(array_values(array("color" => "gold", "size" => "XL")));
Inną opcją, jakbyś nie miał jeszcze wystarczająco dużo, jest połączenie w
assertArraySubset
połączeniu z,assertCount
aby stworzyć swoje potwierdzenie. Twój kod wyglądałby więc mniej więcej tak.self::assertCount(EXPECTED_NUM_ELEMENT, $array); self::assertArraySubset(SUBSET, $array);
W ten sposób jesteś niezależny od porządku, ale nadal zapewniasz, że wszystkie twoje elementy są obecne.
źródło
assertArraySubset
kolejności indeksów znaczenia, więc to nie będzie działać. czyli własnym :: assertArraySubset ([ 'a'] [ 'b', 'a']) będzie fałszywe, ponieważ[0 => 'a']
nie jest w środku[0 => 'b', 1 => 'a']
assertEquals
już radzi sobie z tym, jeśli klucze nie są w tej samej kolejności. Właśnie to przetestowałem.