PHPUnit : 두 배열이 같다고 주장하지만 요소의 순서는 중요하지 않습니다.


132

배열의 요소 순서가 중요하지 않거나 변경 될 수있는 경우 두 배열의 객체가 동일하다고 주장하는 좋은 방법은 무엇입니까?


배열의 객체가 동일하거나 두 배열 모두에 x의 양의 객체 y가 있는지 걱정합니까?
— edorian

@edorian 둘 다 가장 흥미로울 것입니다. 제 경우에는 각 배열에 하나의 객체 y 만 있습니다.
— koen

equal을 정의하십시오 . 정렬 된 객체 해시를 비교하고 싶습니까? 어쨌든 객체 를 정렬 해야 할 것 입니다.
— takeshin

@takeshin ==와 같습니다. 필자의 경우 그것들은 가치 객체이므로 동일성이 필요하지 않습니다. 아마 커스텀 assert 메소드를 만들 수있을 것입니다. 내가 필요로하는 것은 각 배열의 요소 수를 세는 것입니다. 각 요소마다 같은 (==) 존재해야합니다.
— 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

foreach 부분은 불필요합니다. 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에서는 문서화되지 않은 매개 변수 $ canonicalize of assertEquals 메소드를 사용할 수 있습니다 . $ 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 = truePHP는 명명 된 인수를 지원하지 않습니다 - 함수에 매개 변수에 잘못된 내용이 전달합니다. 이것이 실제로하는 일은 세 가지 변수를 설정 한 다음 즉시 값을 함수에 전달하는 것입니다. 이 세 변수가 덮어 쓰기 때문에 이미 로컬 범위에 정의되어 있으면 문제가 발생할 수 있습니다.
— Yi Jiang

11
@ yi-jiang, 그것은 추가 인수의 의미를 설명하는 가장 짧은 방법입니다. 보다 자기 변형적이고 더 깨끗한 변형 $this->assertEquals($array1, $array2, "\$canonicalize = true", 0.0, 10, true);입니다. 1 대신 4 줄을 사용할 수는 있지만 그렇게하지 않았습니다.
— 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는 두 가지 방법으로 모두 수행해야합니다. 한 배열이 다른 배열의 수퍼 세트 인 경우 한 방향으로 array_diff는 비어 있지만 다른 방향으로는 비어 있지 않습니다. $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가 비어
— 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중요하지 않습니다 순서.
— Wilt

1
실제로, 우리는 json_encode를 사용할 필요가 없다는 점만으로 $this->assertSame($exp, $arr); 비슷한 비교를하는 것을 사용할 수 있습니다$this->assertEquals(json_encode($exp), json_encode($arr));
— 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)));

또는 두 가지 주장 (읽기 쉬움) :

// 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 또는 string이고 여러 수준 배열이없는 경우 ....

왜 배열을 정렬하지 않고 문자열로 변환합니까?

    $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']내부에 없기 때문에 false입니다.[0 => 'b', 1 => 'a']
— Robert T.

미안하지만 로버트와 동의해야합니다. 처음에는 이것이 배열을 문자열 키와 비교하는 좋은 솔루션이라고 생각했지만 assertEquals키가 동일한 순서가 아닌 경우 이미 처리합니다. 방금 테스트했습니다.
— Kodos Johnson
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.