PHPUnitManual:10.2
- 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 입니다.
Matcher | 의미 |
PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount any() | 평가 대상 메소드가 0회 이상 실행된 경우에 매치되는 오브젝트를 반환 |
PHPUnit_Framework_MockObject_Matcher_InvokedCount never() | 평가 대상 메소드가 실행되지 않은 경우에 매치되는 오브젝트를 반환 |
PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastOnce atLeastOnce() | 평가 대상 메소드가 최소 1회 이상 실행된 경우에 매치되는 오브젝트를 반환 |
PHPUnit_Framework_MockObject_Matcher_InvokedCount once() | 평가 대상 메소드가 단 한 번 실행된 경우에 매치되는 오브젝트를 반환 |
PHPUnit_Framework_MockObject_Matcher_InvokedCount exactly(int $count) | 평가 대상 메소드가 $count 번 실행된 경우에 매치되는 오브젝트를 반환 |
PHPUnit_Framework_MockObject_Matcher_InvokedAtIndex at(int $index) | 평가 대상 메소드가 $index 번 째로 실행된 경우에 매치되는 오브젝트를 반환 |
표10.1. Matchers |
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.
}
}
?>