PHPUnit: iki dizinin eşit olduğunu iddia edin, ancak öğelerin sırası önemli değil


132

Dizideki öğelerin sırası önemsiz olduğunda veya hatta değişime açık olduğunda, iki nesne dizisinin eşit olduğunu iddia etmenin iyi bir yolu nedir?


Dizideki nesnelerin eşit olmasıyla mı yoksa her iki dizide de x miktarda y nesnesi olmasıyla mı ilgileniyorsunuz?
edorian

@edorian Her ikisi de çok ilginç olurdu. Benim durumumda, her dizide yalnızca bir y nesnesi vardır.
koen

lütfen eşit tanımlayın . Sıralanmış nesne karmalarını karşılaştırmak neye ihtiyacınız var? Muhtemelen yine de nesneleri sıralamanız gerekecek.
takeshin

@takeshin Eşittir ==. Benim durumumda bunlar değer nesneleridir, dolayısıyla aynılık gerekli değildir. Muhtemelen özel bir iddia yöntemi oluşturabilirim. İçinde ihtiyacım olan şey, her dizideki öğelerin sayısını saymaktır ve her iki öğe için de eşit (==) olmalıdır.
koen

7
Aslında, PHPUnit 3.7.24'te $ this-> assertEquals, dizinin hangi sırayla aynı anahtarları ve değerleri içerdiğini varsayar.
Dereckson

Yanıtlar:


38

Bunu yapmanın en temiz yolu, phpunit'i yeni bir iddia yöntemiyle genişletmek olacaktır. Ama işte şimdilik daha basit bir yol için bir fikir. Test edilmemiş kod, lütfen doğrulayın:

Uygulamanızda bir yerde:

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

Testinizde:

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

Craig, başlangıçta denediğim şeye yakınsın. Aslında array_diff ihtiyacım olan şeydi, ancak nesneler için işe yaramıyor gibi görünüyor. Özel iddiamı
koen

Doğru bağlantı şimdi https ile ve www olmadan: phpunit.de/manual/current/en/extending-phpunit.html
Xavi Montero

foreach kısmı gereksizdir - array_diff_assoc hem anahtarları hem de değerleri zaten karşılaştırır. DÜZENLEME: ve count(array_diff_assoc($b, $a))ayrıca kontrol etmeniz gerekir .
JohnSmith

212

PHPUnit 7.5'e eklenen assertEqualsCanonicalizing yöntemini kullanabilirsiniz . Bu yöntemi kullanarak dizileri karşılaştırırsanız, bu diziler PHPUnit dizileri karşılaştırıcısının kendisine göre sıralanır.

Kod örneği:

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'in eski sürümlerinde, assertEquals yönteminin belgelenmemiş $ canonicalize parametresini kullanabilirsiniz . Eğer geçerseniz $ canonicalize = true , aynı etkiyi elde edecek:

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'in en son sürümündeki diziler karşılaştırıcı kaynak kodu: https://github.com/sebastianbergmann/comparator/blob/master/src/ArrayComparator.php#L46


10
Fantastik. Neden kabul edilen cevap bu değil, @koen?
rinogo

7
$delta = 0.0, $maxDepth = 10, $canonicalize = trueParametreleri işleve geçirmek için kullanmak yanıltıcıdır - PHP adlandırılmış argümanları desteklemez. Bunun aslında yaptığı şey, bu üç değişkeni ayarlamak ve ardından değerlerini hemen işleve geçirmektir. Bu üç değişken, üzerine yazılacağı için yerel kapsamda zaten tanımlanmışsa sorunlara neden olacaktır.
Yi Jiang

11
@ yi-jiang, ek argümanların anlamını açıklamanın en kısa yoludur. Daha öz açıklayıcı sonra daha temiz varyantı var: $this->assertEquals($array1, $array2, "\$canonicalize = true", 0.0, 10, true);. 1 yerine 4 satır kullanabilirdim ama bunu yapmadım.
pryazhnikov

8
Bu çözümün anahtarları atacağına işaret etmiyorsunuz.
Odalrick

8
$canonicalizebunun kaldırılacağını unutmayın : github.com/sebastianbergmann/phpunit/issues/3342 ve assertEqualsCanonicalizing()onun yerini alacaktır.
koen

35

Benim sorunum 2 diziye sahip olmamdı (dizi anahtarları benim için önemli değil, sadece değerler).

Örneğin eğer test etmek istedim

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

aynı içeriğe sahipti (sipariş benim için uygun değil)

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

Bu yüzden array_diff kullandım .

Nihai sonuç oldu (diziler eşitse, fark boş bir dizi ile sonuçlanacaktır). Lütfen farkın her iki şekilde hesaplandığını unutmayın (Teşekkürler @beret, @GordonM)

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

Daha ayrıntılı bir hata mesajı için (hata ayıklama sırasında), şu şekilde de test edebilirsiniz (teşekkürler @ DenilsonSá):

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

İçinde böcek bulunan eski sürüm:

$ this-> assertEmpty (array_diff ($ dizi2, $ dizi1));


Bu yaklaşımın konusu, şundan $array1daha fazla değere sahipse , $array2dizi değerleri eşit olmasa bile boş dizi döndürmesidir. Ayrıca emin olmak için dizi boyutunun aynı olduğunu test etmelisiniz.
petrkotek

3
Array_diff veya array_diff_assoc'u her iki şekilde de yapmalısınız. Bir dizi diğerinin üst kümesiyse, bir yöndeki dizi_farkı boş olur, diğerinde boş olmaz. $a1 = [1,2,3,4,5]; $a2 = [1,3,5]; var_dump (array_diff ($a1, $a2)); var_dump (array_diff ($a2, $a1))
GordonM

2
assertEmptydiziyi boş değilse yazdırmaz, bu da testlerde hata ayıklama sırasında sakıncalıdır. Kullanmanızı öneririm:, $this->assertSame(array_diff($expected, $actual), array_diff($actual, $expected), $message);çünkü bu, minimum ekstra kodla en yararlı hata mesajını yazdıracaktır. Bu işe yarar
Denilson Sá Maia

Array_diff'in karşılaştırma için her değeri dizeye dönüştürdüğünü unutmayın.
Konstantin Pelepelin

@Checat'e eklemek için: Array to string conversionBir diziyi bir dizeye dönüştürmeye çalıştığınızda bir mesaj alacaksınız . Bunu implode
aşmanın

20

Bir başka olasılık:

  1. Her iki diziyi de sırala
  2. Onları bir dizeye dönüştürün
  3. Her iki dizenin de eşit olduğunu iddia edin

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

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

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

Dizilerden herhangi biri nesneler içeriyorsa, json_encode yalnızca genel özellikleri kodlar. Bu yine de işe yarayacak, ancak yalnızca eşitliği belirleyen tüm mülkler halka açıksa. Özel özelliklerin json_encodingini kontrol etmek için aşağıdaki arayüze bir göz atın. php.net/manual/en/class.jsonserializable.php
Westy92

1
Bu, sıralama olmadan bile çalışır. İçin assertEqualsönemli değil sırayla.
Wilt

1
Aslında, tek fark json_encode kullanmak zorunda olmadığımız için $this->assertSame($exp, $arr); benzer bir karşılaştırma yapan hangisini $this->assertEquals(json_encode($exp), json_encode($arr));kullanabiliriz
maxwells

15

Basit yardımcı yöntem

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

Veya diziler eşit olmadığında daha fazla hata ayıklama bilgisine ihtiyacınız varsa

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

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

8

Dizi sıralanabiliyorsa, eşitliği kontrol etmeden önce ikisini de sıralarım. Değilse, onları bir tür setlere dönüştürür ve karşılaştırırdım.


6

Array_diff () kullanarak :

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

Veya 2 iddia ile (daha kolay okunur):

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

Bu akıllı :)
Christian

Tam olarak aradığım şey. Basit.
Abdul Maye

6

Siparişi önemsemeseniz bile, bunu hesaba katmak daha kolay olabilir:

Deneyin:

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

5

Testlerimizde aşağıdaki sarmalayıcı yöntemini kullanıyoruz:

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

Anahtarlar aynıysa ancak sıra dışıysa bu sorunu çözmelidir.

Anahtarları aynı sırayla almanız ve sonuçları karşılaştırmanız yeterlidir.

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

Verilen çözümler benim için işi yapmadı çünkü çok boyutlu diziyi işleyebilmek ve iki dizi arasında neyin farklı olduğuna dair net bir mesaja sahip olmak istedim.

İşte benim fonksiyonum

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

Sonra kullanmak için

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

1

İlk önce tüm anahtarları çok boyutlu bir diziden almak için bazı basit kodlar yazdım:

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

Ardından, anahtarların sırasına bakılmaksızın aynı şekilde yapılandırıldıklarını test etmek için:

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

HTH


0

Değerler yalnızca int veya dizelerden ibaretse ve birden çok düzey dizisi yoksa ...

Neden dizileri sıralamak, dizgeye dönüştürmek değil ...

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

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

... ve sonra dizeyi karşılaştırın:

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

-2

Yalnızca dizinin değerlerini test etmek istiyorsanız şunları yapabilirsiniz:

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

1
Ne yazık ki bu, "yalnızca değerleri" değil, değerlerin hem değerlerini hem de sırasını test etmektir. Örneğinecho("<pre>"); print_r(array_values(array("size" => "XL", "color" => "gold"))); print_r(array_values(array("color" => "gold", "size" => "XL")));
Pocketsand

-3

Başka bir seçenek, zaten yeterince yoktu sanki birleştirmek için assertArraySubsetkombine assertCountiddianızı yapmak. Yani kodunuz şuna benzer.

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

Bu şekilde düzenden bağımsız olursunuz, ancak yine de tüm öğelerinizin mevcut olduğunu iddia edersiniz.


Gelen assertArraySubsetendeksler sırasına o işi olmaz bu yüzden önemli. yani öz :: assertArraySubset ([ 'a'], [ 'b', 'a']), çünkü yanlış olacaktır [0 => 'a']içinde değil[0 => 'b', 1 => 'a']
Robert T.

Üzgünüm ama Robert ile aynı fikirde olmalıyım. İlk başta bunun dizileri dizgi anahtarlarıyla karşılaştırmak için iyi bir çözüm olacağını düşündüm, ancak assertEqualsanahtarlar aynı sırada değilse bunu zaten hallediyor. Daha yeni test ettim.
Kodos Johnson
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.