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.