php 使用 PHPUnit 模拟私有方法

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/5937845/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-25 22:57:33  来源:igfitidea点击:

Mock private method with PHPUnit

phpmockingphpunit

提问by Tony

I have a question about using PHPUnit to mock a private method inside a class. Let me introduce with an example:

我有一个关于使用 PHPUnit 来模拟类中的私有方法的问题。我举个例子来介绍一下:

class A {
  public function b() { 
    // some code
    $this->c(); 
    // some more code
  }

  private function c(){ 
    // some code
  }
}

How can I stub the result of the private method to test the some more codepart of the public function.

我如何存根私有方法的结果来测试公共函数的更多代码部分。

Solved partially reading here

解决部分阅读here

回答by edorian

Usually you just don't test or mock the private & protected methods directy.

通常你只是不直接测试或模拟私有和受保护的方法。

What you want to test is the publicAPI of your class. Everything else is an implementation detail for your class and should not "break" your tests if you change it.

您要测试的是您班级的公共API。其他所有内容都是您的类的实现细节,如果您更改它,则不应“破坏”您的测试。

That also helps you when you notice that you "can't get 100% code coverage" because you might have code in your class that you can't execute by calling the public API.

当您注意到“无法获得 100% 的代码覆盖率”时,这也会对您有所帮助,因为您的类中可能有无法通过调用公共 API 执行的代码。



You usually don't want to do this

你通常不想这样做

But if your class looks like this:

但是如果你的类看起来像这样:

class a {

    public function b() {
        return 5 + $this->c();
    }

    private function c() {
        return mt_rand(1,3);
    }
}

i can see the need to want to mock out c() since the "random" function is global state and you can't test that.

我可以看到需要模拟 c(),因为“随机”函数是全局状态,您无法对其进行测试。

The "clean?/verbose?/overcomplicated-maybe?/i-like-it-usually" Solution

“干净?/冗长?/过度复杂-也许?/我喜欢它-通常”的解决方案

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);
    }
}

now there is no more need to mock "c()" out since it does not contain any globals and you can test nicely.

现在不再需要模拟“c()”,因为它不包含任何全局变量,您可以很好地进行测试。



If you don't want to do or can't remove the global state from your private function (bad thing bad reality or you definition of bad might be different) that you cantest against the mock.

如果您不想或不能从您的私有函数中删除全局状态(坏事坏现实或您对坏的定义可能不同),您可以针对模拟进行测试。

// 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));

and run your tests against this mock since the only function you take out/mock is "c()".

并针对此模拟运行您的测试,因为您取出/模拟的唯一功能是“c()”。



To quote the "Pragmatic Unit Testing" book:

引用“实用单元测试”一书:

"In general, you don't want to break any encapsulation for the sake of testing (or as Mom used to say, "don't expose your privates!"). Most of the time, you should be able to test a class by exercising its public methods. If there is significant functionality that is hidden behind private or protected access, that might be a warning sign that there's another class in there struggling to get out."

“一般来说,你不想为了测试而破坏任何封装(或者像妈妈常说的,“不要暴露你的隐私!”)。大多数时候,你应该能够测试一个类“通过行使其公共方法。如果有重要的功能隐藏在私有或受保护的访问之后,这可能是一个警告信号,表明那里有另一个类正在努力摆脱。”



Some more: Why you don't want test private methods.

多一点: Why you don't want test private methods.

回答by doctore

You can test private methodsbut you can't simulate (mock) the running of this methods.

您可以测试私有方法,但不能模拟(模拟)此方法的运行。

Furthermore, the reflection does not allow you to convert a private method to a protected or public method. setAccessibleonly allows you to invoke the original method.

此外,反射不允许您将私有方法转换为受保护或公共方法。setAccessible只允许您调用原始方法。

Alternatively, you could use runkitfor rename the private methods and include a "new implementation". However, these features are experimental and their use is not recommended.

或者,您可以使用runkit重命名私有方法并包含“新实现”。但是,这些功能是实验性的,不推荐使用。

回答by David Harkness

You can use reflectionand setAccessible()in your tests to allow you to set the internal state of your object in such a way that it will return what you want from the private method. You'll need to be on PHP 5.3.2.

您可以使用反射setAccessible()在您的测试中允许您设置对象的内部状态,以便它将从私有方法返回您想要的内容。您需要使用 PHP 5.3.2。

$fixture = new MyClass(...);
$reflector = new ReflectionProperty('MyClass', 'myPrivateProperty');
$reflector->setAccessible(true);
$reflector->setValue($fixture, 'value');
// test $fixture ...

回答by Archit Rastogi

You can get mock of protected method , so if you can convert C to protected then this code will help.

您可以模拟受保护的方法,因此如果您可以将 C 转换为受保护的,那么此代码将有所帮助。

 $mock = $this->getMockBuilder('A')
                  ->disableOriginalConstructor()
                  ->setMethods(array('C'))
                  ->getMock();

    $response = $mock->B();

This will definitely work , It worked for me . Then For covering protected method C you can use reflection classes.

这肯定会起作用,它对我有用。然后为了覆盖受保护的方法 C,您可以使用反射类。

回答by Edson Medina

Assuming that you need to test $myClass->privateMethodX($arg1, $arg2), you can do this with reflection:

假设你需要测试 $myClass->privateMethodX($arg1, $arg2),你可以用反射来做到这一点:

$class = new ReflectionClass ($myClass);
$method = $class->getMethod ('privateMethodX');
$method->setAccessible(true);
$output = $method->invoke ($myClass, $arg1, $arg2);

回答by Mark McEver

Here's a variation of the other answers that can be used to make such calls one line:

这是可用于在一行中进行此类调用的其他答案的变体:

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);
}

回答by Torge

I came up with this general purpose class for my case:

我为我的案例想出了这个通用类:

/**
 * @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);
    }
}

With this class you can now easily & transparently liberate all private functions/fields on any object.

有了这个类,您现在可以轻松透明地释放任何对象上的所有私有函数/字段。

$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);

If performance is important, it can be improved by caching the properties/methods called in the Liberator class.

如果性能很重要,可以通过缓存 Liberator 类中调用的属性/方法来提高性能。

回答by Asaph

One option would be to make c()protectedinstead of privateand then subclass and override c(). Then test with your subclass. Another option would be to refactor c()out into a different class that you can inject into A(this is called dependency injection). And then inject a testing instance with a mock implementation of c()in your unit test.

一种选择是 makec()protected而不是private然后子类和 override c()。然后用你的子类测试。另一种选择是重构c()为您可以注入的不同类A(这称为依赖注入)。然后c()在单元测试中注入一个带有模拟实现的测试实例。

回答by Thellimist

An alternative solution is to change your private method to a protected method and then mock.

另一种解决方案是将您的私有方法更改为受保护的方法,然后进行模拟。

$myMockObject = $this->getMockBuilder('MyMockClass')
        ->setMethods(array('__construct'))
        ->setConstructorArgs(array("someValue", 5))
        ->setMethods(array('myProtectedMethod'))
        ->getMock();

$response = $myMockObject->myPublicMethod();

where myPublicMethodcalls myProtectedMethod. Unfortunately we can not do this with private methods since setMethodscan not find a private method where as it can find a protected method

哪里myPublicMethod调用myProtectedMethod。不幸的是,我们不能用私有方法来做到这一点,因为setMethods找不到私有方法,因为它可以找到受保护的方法

回答by jgmjgm

You can use anonymous classes using PHP 7.

您可以使用 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.

在早期版本的 PHP 中,您可以创建一个扩展基类的测试类。