PHPUnitManual:10.2

From 흡혈양파의 번역工房
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.
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