PHPUnit으로 개인 메서드 모의
PHPUnit을 사용하여 클래스 내부의 개인 메서드를 모의하는 것에 대한 질문이 있습니다. 예를 들어 소개하겠습니다.
class A {
public function b() {
// some code
$this->c();
// some more code
}
private function c(){
// some code
}
}
public 함수의 더 많은 코드 부분 을 테스트하기 위해 private 메서드의 결과를 어떻게 스텁 할 수 있습니까 ?
여기에서 부분적으로 읽기 해결
일반적으로 개인 및 보호 방법을 직접 테스트하거나 모의하지 않습니다.
테스트하려는 것은 클래스 의 공용 API입니다. 다른 모든 것은 클래스의 구현 세부 사항이며 변경해도 테스트를 "중단"해서는 안됩니다.
또한 공개 API를 호출하여 실행할 수없는 코드가 클래스에있을 수 있기 때문에 "100 % 코드 커버리지를 얻을 수 없음"을 발견 할 때도 도움이됩니다.
일반적으로이 작업을 원하지 않습니다.
그러나 수업이 다음과 같은 경우 :
class a {
public function b() {
return 5 + $this->c();
}
private function c() {
return mt_rand(1,3);
}
}
"무작위"함수가 전역 상태이고이를 테스트 할 수 없기 때문에 c ()를 모의하고 싶을 필요가 있음을 알 수 있습니다.
"깨끗한? / verbose? / overcomplicated-maybe? / i-like-it-usually"솔루션
class a {
public function __construct(RandomGenerator $foo) {
$this->foo = $foo;
}
public function b() {
return 5 + $this->c();
}
private function c() {
return $this->foo->rand(1,3);
}
}
이제 더 이상 "c ()"를 조롱 할 필요가 없습니다. 전역이 포함되어 있지 않기 때문에 멋지게 테스트 할 수 있습니다.
개인 함수에서 전역 상태를 제거하고 싶지 않거나 제거 할 수없는 경우 (나쁜 현실 또는 나쁜 정의가 다를 수 있음) 모의에 대해 테스트 할 수 있습니다 .
// maybe set the function protected for this to work
$testMe = $this->getMock("a", array("c"));
$testMe->expects($this->once())->method("c")->will($this->returnValue(123123));
당신이 꺼내거나 모의하는 유일한 함수는 "c ()"이기 때문입니다.
"Pragmatic Unit Testing"책을 인용하려면 :
"일반적으로 테스트를 위해 캡슐화를 깨고 싶지는 않습니다 (또는 엄마가"개인 정보를 노출하지 마십시오! "라고 말했듯이). 대부분의 경우 클래스를 테스트 할 수 있어야합니다. 비공개 또는 보호 된 액세스 뒤에 숨겨져있는 중요한 기능이있는 경우 다른 클래스가 빠져 나가기 위해 고군분투하고 있다는 경고 신호일 수 있습니다. "
좀 더: Why you don't want test private methods.
개인 메서드 를 테스트 할 수 있지만이 메서드의 실행을 시뮬레이션 (모의) 할 수는 없습니다.
또한 리플렉션을 사용하면 개인 메서드를 보호 된 메서드 나 공용 메서드로 변환 할 수 없습니다. setAccessible 은 원래 메서드를 호출하는 것만 허용합니다.
또는 runkit 을 사용 하여 개인 메소드의 이름을 바꾸고 "새 구현"을 포함 할 수 있습니다 . 그러나 이러한 기능은 실험적이므로 사용하지 않는 것이 좋습니다.
당신이 사용할 수있는 반사 하고 setAccessible()
당신이 당신이 개인 방법에서 원하는 반환하는 방식으로 개체의 내부 상태를 설정할 수 있도록 당신의 검사 결과에있다. PHP 5.3.2에 있어야합니다.
$fixture = new MyClass(...);
$reflector = new ReflectionProperty('MyClass', 'myPrivateProperty');
$reflector->setAccessible(true);
$reflector->setValue($fixture, 'value');
// test $fixture ...
protected method의 모의를 얻을 수 있으므로 C를 protected로 변환 할 수 있다면이 코드가 도움이 될 것입니다.
$mock = $this->getMockBuilder('A')
->disableOriginalConstructor()
->setMethods(array('C'))
->getMock();
$response = $mock->B();
이것은 확실히 작동 할 것이다. 그것은 나를 위해 일했다. 그런 다음 보호 된 메서드 C를 다루기 위해 리플렉션 클래스를 사용할 수 있습니다.
$ myClass-> privateMethodX ($ arg1, $ arg2)를 테스트해야한다고 가정하면 리플렉션을 사용하여이를 수행 할 수 있습니다.
$class = new ReflectionClass ($myClass);
$method = $class->getMethod ('privateMethodX');
$method->setAccessible(true);
$output = $method->invoke ($myClass, $arg1, $arg2);
다음은 이러한 전화를 한 줄로 만드는 데 사용할 수있는 다른 답변의 변형입니다.
public function callPrivateMethod($object, $methodName)
{
$reflectionClass = new \ReflectionClass($object);
$reflectionMethod = $reflectionClass->getMethod($methodName);
$reflectionMethod->setAccessible(true);
$params = array_slice(func_get_args(), 2); //get all the parameters after $methodName
return $reflectionMethod->invokeArgs($object, $params);
}
내 경우에이 범용 클래스를 생각해 냈습니다.
/**
* @author Torge Kummerow
*/
class Liberator {
private $originalObject;
private $class;
public function __construct($originalObject) {
$this->originalObject = $originalObject;
$this->class = new ReflectionClass($originalObject);
}
public function __get($name) {
$property = $this->class->getProperty($name);
$property->setAccessible(true);
return $property->getValue($this->originalObject);
}
public function __set($name, $value) {
$property = $this->class->getProperty($name);
$property->setAccessible(true);
$property->setValue($this->originalObject, $value);
}
public function __call($name, $args) {
$method = $this->class->getMethod($name);
$method->setAccessible(true);
return $method->invokeArgs($this->originalObject, $args);
}
}
이 클래스를 사용하면 이제 모든 객체의 모든 개인 함수 / 필드를 쉽고 투명하게 해제 할 수 있습니다.
$myObject = new Liberator(new MyObject());
/* @var $myObject MyObject */ //Usefull for code completion in some IDEs
//Writing to a private field
$myObject->somePrivateField = "testData";
//Reading a private field
echo $myObject->somePrivateField;
//calling a private function
$result = $myObject->somePrivateFunction($arg1, $arg2);
성능이 중요한 경우 Liberator 클래스에서 호출 된 속성 / 메서드를 캐싱하여 개선 할 수 있습니다.
한 가지 옵션은 c()
protected
대신 만들고 private
하위 클래스를 만들고 재정의하는 것 c()
입니다. 그런 다음 하위 클래스로 테스트하십시오. 또 다른 옵션은 c()
주입 할 수있는 다른 클래스로 리팩토링 하는 것 A
입니다 (이를 종속성 주입이라고 함). 그런 다음 c()
단위 테스트에서 의 모의 구현으로 테스트 인스턴스를 삽입 하십시오.
다른 해결책은 개인 방법을 보호 된 방법으로 변경 한 다음 모의하는 것입니다.
$myMockObject = $this->getMockBuilder('MyMockClass')
->setMethods(array('__construct'))
->setConstructorArgs(array("someValue", 5))
->setMethods(array('myProtectedMethod'))
->getMock();
$response = $myMockObject->myPublicMethod();
where myPublicMethod
calls myProtectedMethod
. Unfortunately we can not do this with private methods since setMethods
can not find a private method where as it can find a protected method
You can use anonymous classes using PHP 7.
$mock = new class Concrete {
private function bob():void
{
}
};
In prior versions of PHP you can make a test class extending the base class.
참고URL : https://stackoverflow.com/questions/5937845/mock-private-method-with-phpunit
'Program Tip' 카테고리의 다른 글
bash의 백틱에 해당하는 cmd / powershell은 무엇입니까? (0) | 2020.11.16 |
---|---|
Subversion에서 삭제 된 분기를 복원해야합니다. (0) | 2020.11.16 |
Emacs에 공백을 표시하려면 어떻게해야합니까? (0) | 2020.11.15 |
Path.GetTempPath ()의 반환 값을 결정하는 것은 무엇입니까? (0) | 2020.11.15 |
g ++에서 프로필 기반 최적화를 사용하는 방법은 무엇입니까? (0) | 2020.11.15 |