PHPUnitManual:10.2

From 흡혈양파의 번역工房
Jump to: navigation, search
10.2 Mock 오브젝트

실제 객체를 대신해서, 메소드가 호출되는 등의 기대되는 동작을 검증하는 테스트 더블을 mock 이라고 합니다.


mock 오브젝트는 SUT 의 간접 출력 내용을 검증하기 위해 사용하는 관측 지점입니다. 일반적으로 mock 오브젝트에는 테스트용 stub 기능도 포함됩니다. 테스트에 실패하지 않은 경우, 간접 출력의 검증용 값을 SUT 에 반환하는 기능입니다. 따라서, mock 오브젝트는 테스트용 stub 에 단순히 검증 기능을 추가한 것이 아닙니다. 그 이외의 용도로도 사용할 수 있습니다.


한가지 예를 들겠습니다. 여기서는 다른 오브젝트를 관찰하는 한 오브젝트의 특정 메소드 (update() 가 적절하게 호출되었는지를 조사하고자 합니다. 예10.10 "테스트 대상 시스템 (SUT) 에 포함된 Subject 클래스와 Observer 클래스" 는 테스트 대상 시스템 (SUT) 에 포함된 Subject 클래스와 Observer 클래스의 코드입니다.


예10.10 테스트 대상 시스템 (SUT) 에 포함된 Subject 클래스와 Observer 클래스

<?php
class Subject
{
    protected $observers = array();
 
    public function attach(Observer $observer)
    {
        $this->observers[] = $observer;
    }
 
    public function doSomething()
    {
        // Do something.
        // ...
 
        // Notify observers that we did something.
        $this->notify('something');
    }
 
    public function doSomethingBad()
    {
        foreach ($this->observers as $observer) {
            $observer->reportError(42, 'Something bad happened', $this);
        }
    }
 
    protected function notify($argument)
    {
        foreach ($this->observers as $observer) {
            $observer->update($argument);
        }
    }
 
    // Other methods.
}
 
class Observer
{
    public function update($argument)
    {
        // Do something.
    }
 
    public function reportError($errorCode, $errorMessage, Subject $subject)
    {
        // Do something
    }
 
    // Other methods.
}
?>


예10.11 "한 메소드가 지정된 인수로 한 번만 호출되는 것을 확인하는 테스트" 는 mock 오브젝트를 생성하여 Subject 오브젝트와 Observer 오브젝트의 상호 작용을 테스트하는 방법입니다.


먼저, PHPUnit_Framework_TestCase 클래스의 getMock() 메소드를 사용하여 Observer 의 mock 오브젝트를 생성합니다. getMock() 메소드의 2번째 옵션 인수에 배열을 넘기고 있기 때문에, Observer 클래스의 update() 메소드만이 mock 으로 구현됩니다.


예10.11 한 메소드가 지정된 인수로 한 번만 호출되는 것을 확인하는 테스트

<?php
class SubjectTest extends PHPUnit_Framework_TestCase
{
    public function testObserversAreUpdated()
    {
        // Create a mock for the Observer class,
        // only mock the update() method.
        $observer = $this->getMock('Observer', array('update'));
 
        // Set up the expectation for the update() method
        // to be called only once and with the string 'something'
        // as its parameter.
        $observer->expects($this->once())
                 ->method('update')
                 ->with($this->equalTo('something'));
 
        // Create a Subject object and attach the mocked
        // Observer object to it.
        $subject = new Subject;
        $subject->attach($observer);
 
        // Call the doSomething() method on the $subject object
        // which we expect to call the mocked Observer object's
        // update() method with the string 'something'.
        $subject->doSomething();
    }
}
?>


with() 메소드에는 임의의 수의 인수를 넘길 수 있습니다. 이는 mock 대상 메소드의 인수 수에 대응합니다. 메소드의 인수에 대해서 단순한 매칭이 아닌 보다 고도한 제약을 지정할 수도 있습니다.


예10.12 인수를 가지는 메소드가 호출되는 것을 다양한 제약을 부여하여 테스트하는 예

<?php
class SubjectTest extends PHPUnit_Framework_TestCase
{
    public function testErrorReported()
    {
        // Create a mock for the Observer class, mocking the
        // reportError() method
        $observer = $this->getMock('Observer', array('reportError'));
 
        $observer->expects($this->once())
                 ->method('reportError')
                 ->with($this->greaterThan(0),
                        $this->stringContains('Something'),
                        $this->anything());
 
        $subject = new Subject;
        $subject->attach($observer);
 
        // The doSomethingBad() method should report an error to the observer
        // via the reportError() method
        $subject->doSomethingBad();
    }
}
?>


표4.3 "제약" 은 메소드의 인수에 적용 가능한 제약에 대해서, 표10.1 "Matchers" 는 기동 횟수를 지정하기 위해 사용 가능한 matcher 입니다.


getMockForAbstractClass() 메소드는 추상 클래스의 mock 오브젝트를 반환합니다. 이 클래스의 모든 추상 메소드가 mock 대상입니다. 이를 사용하여 추상 클래스의 구상(concrete) 메소드를 테스트할 수 있습니다.


예10.13: 추상 클래스의 구상 메소드의 테스트

<?php
abstract class AbstractClass
{
    public function concreteMethod()
    {
        return $this->abstractMethod();
    }
 
    public abstract function abstractMethod();
}
 
class AbstractClassTest extends PHPUnit_Framework_TestCase
{
    public function testConcreteMethod()
    {
        $stub = $this->getMockForAbstractClass('AbstractClass');
        $stub->expects($this->any())
             ->method('abstractMethod')
             ->will($this->returnValue(TRUE));
 
        $this->assertTrue($stub->concreteMethod());
    }
}
?>


예10.14 한 메소드가 한 번 호출되고, 동일 오브젝트가 넘겨진 것을 조사하는 테스트

<?php
class FooTest extends PHPUnit_Framework_TestCase
{
    public function testIdenticalObjectPassed()
    {
        $expectedObject = new stdClass;
 
        $mock = $this->getMock('stdClass', array('foo'));
        $mock->expects($this->once())
             ->method('foo')
             ->with($this->identicalTo($expectedObject));
 
        $mock->foo($expectedObject);
    }
}
?>


예10.15: 인수의 clone 을 유효로 한 mock 오브젝트의 생성

<?php
class FooTest extends PHPUnit_Framework_TestCase
{
    public function testIdenticalObjectPassed()
    {
        $cloneArguments = true;
 
        $mock = $this->getMock(
            'stdClass',
            array(),
            array(),
            '',
            FALSE,
            TRUE,
            TRUE,
            $cloneArguments
        );
 
        // or using the mock builder
        $mock = $this->getMockBuilder('stdClass')->enableArgumentCloning()->getMock();
 
        // now your mock clones parameters so the identicalTo constraint will fail.
    }
}
?>


Notes