php 使用 PHPUnit 测试受保护方法的最佳实践

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/249664/
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-24 22:03:53  来源:igfitidea点击:

Best practices to test protected methods with PHPUnit

phpunit-testingphpunit

提问by GrGr

I found the discussion on Do you test private methodinformative.

我发现有关您是否测试私有方法的讨论内容丰富。

I have decided, that in some classes, I want to have protected methods, but test them. Some of these methods are static and short. Because most of the public methods make use of them, I will probably be able to safely remove the tests later. But for starting with a TDD approach and avoid debugging, I really want to test them.

我已经决定,在某些类中,我想要受保护的方法,但要测试它们。其中一些方法是静态的和简短的。因为大多数公共方法都使用它们,所以我以后可能可以安全地删除测试。但是为了从 TDD 方法开始并避免调试,我真的很想测试它们。

I thought of the following:

我想到了以下几点:

  • Method Objectas adviced in an answerseems to be overkill for this.
  • Start with public methods and when code coverage is given by higher level tests, turn them protected and remove the tests.
  • Inherit a class with a testable interface making protected methods public
  • 答案中建议的方法对象对此似乎有些矫枉过正。
  • 从公共方法开始,当代码覆盖率由更高级别的测试给出时,将它们保护起来并删除测试。
  • 继承一个具有可测试接口的类,使受保护的方法成为公共

Which is best practice? Is there anything else?

哪个是最佳实践?还有别的事吗?

It seems, that JUnit automatically changes protected methods to be public, but I did not have a deeper look at it. PHP does not allow this via reflection.

看起来,JUnit 会自动将受保护的方法更改为公共方法,但我没有更深入地研究它。PHP 不允许通过反射这样做

回答by uckelman

If you're using PHP5 (>= 5.3.2) with PHPUnit, you can test your private and protected methods by using reflection to set them to be public prior to running your tests:

如果您将 PHP5 (>= 5.3.2) 与 PHPUnit 一起使用,您可以在运行测试之前使用反射将它们设置为公共方法来测试私有和受保护的方法:

protected static function getMethod($name) {
  $class = new ReflectionClass('MyClass');
  $method = $class->getMethod($name);
  $method->setAccessible(true);
  return $method;
}

public function testFoo() {
  $foo = self::getMethod('foo');
  $obj = new MyClass();
  $foo->invokeArgs($obj, array(...));
  ...
}

回答by troelskn

You seem to be aware already, but I'll just restate it anyway; It's a bad sign, if you need to test protected methods. The aim of a unit test, is to test the interface of a class, and protected methods are implementation details. That said, there are cases where it makes sense. If you use inheritance, you can see a superclass as providing an interface for the subclass. So here, you would have to test the protected method (But never a privateone). The solution to this, is to create a subclass for testing purpose, and use this to expose the methods. Eg.:

你似乎已经知道了,但我还是要重申一下;如果您需要测试受保护的方法,这是一个不好的迹象。单元测试的目的是测试类的接口,受保护的方法是实现细节。也就是说,在某些情况下它是有道理的。如果使用继承,则可以将超类视为为子类提供接口。因此,在这里,您必须测试受保护的方法(但绝不是私有方法)。对此的解决方案是创建一个用于测试目的的子类,并使用它来公开方法。例如。:

class Foo {
  protected function stuff() {
    // secret stuff, you want to test
  }
}

class SubFoo extends Foo {
  public function exposedStuff() {
    return $this->stuff();
  }
}

Note that you can always replace inheritance with composition. When testing code, it's usually a lot easier to deal with code that uses this pattern, so you may want to consider that option.

请注意,您始终可以用组合替换继承。测试代码时,处理使用此模式的代码通常要容易得多,因此您可能需要考虑该选项。

回答by robert.egginton

teastburnhas the right approach. Even simpler is to call the method directly and return the answer:

Teastburn有正确的方法。更简单的是直接调用方法并返回答案:

class PHPUnitUtil
{
  public static function callMethod($obj, $name, array $args) {
        $class = new \ReflectionClass($obj);
        $method = $class->getMethod($name);
        $method->setAccessible(true);
        return $method->invokeArgs($obj, $args);
    }
}

You can call this simply in your tests by:

您可以通过以下方式在测试中简单地调用它:

$returnVal = PHPUnitUtil::callMethod(
                $this->object,
                '_nameOfProtectedMethod', 
                array($arg1, $arg2)
             );

回答by teastburn

I'd like to propose a slight variation to getMethod() defined in uckelman's answer.

我想对uckelman's answer 中定义的 getMethod() 提出一个细微的变化。

This version changes getMethod() by removing hard-coded values and simplifying usage a little. I recommend adding it to your PHPUnitUtil class as in the example below or to your PHPUnit_Framework_TestCase-extending class (or, I suppose, globally to your PHPUnitUtil file).

此版本通过删除硬编码值并稍微简化使用来更改 getMethod()。我建议将它添加到您的 PHPUnitUtil 类,如下例所示,或添加到您的 PHPUnit_Framework_TestCase 扩展类(或者,我想,全局添加到您的 PHPUnitUtil 文件)。

Since MyClass is being instantiated anyways and ReflectionClass can take a string or an object...

由于无论如何 MyClass 正在被实例化并且 ReflectionClass 可以采用字符串或对象......

class PHPUnitUtil {
    /**
     * Get a private or protected method for testing/documentation purposes.
     * How to use for MyClass->foo():
     *      $cls = new MyClass();
     *      $foo = PHPUnitUtil::getPrivateMethod($cls, 'foo');
     *      $foo->invoke($cls, $...);
     * @param object $obj The instantiated instance of your class
     * @param string $name The name of your private/protected method
     * @return ReflectionMethod The method you asked for
     */
    public static function getPrivateMethod($obj, $name) {
      $class = new ReflectionClass($obj);
      $method = $class->getMethod($name);
      $method->setAccessible(true);
      return $method;
    }
    // ... some other functions
}

I also created an alias function getProtectedMethod() to be explicit what is expected, but that one's up to you.

我还创建了一个别名函数 getProtectedMethod() 来明确预期的内容,但这取决于您。

Cheers!

干杯!

回答by Michael Johnson

I think troelskn is close. I would do this instead:

我认为 troelskn 很接近。我会这样做:

class ClassToTest
{
   protected function testThisMethod()
   {
     // Implement stuff here
   }
}

Then, implement something like this:

然后,实现这样的事情:

class TestClassToTest extends ClassToTest
{
  public function testThisMethod()
  {
    return parent::testThisMethod();
  }
}

You then run your tests against TestClassToTest.

然后针对 TestClassToTest 运行测试。

It should be possible to automatically generate such extension classes by parsing the code. I wouldn't be surprised if PHPUnit already offers such a mechanism (though I haven't checked).

应该可以通过解析代码自动生成这样的扩展类。如果 PHPUnit 已经提供了这样的机制,我不会感到惊讶(尽管我还没有检查过)。

回答by David Harkness

You can indeed use __call() in a generic fashion to access protected methods. To be able to test this class

您确实可以以通用方式使用 __call() 来访问受保护的方法。为了能够测试这个类

class Example {
    protected function getMessage() {
        return 'hello';
    }
}

you create a subclass in ExampleTest.php:

在 ExampleTest.php 中创建一个子类:

class ExampleExposed extends Example {
    public function __call($method, array $args = array()) {
        if (!method_exists($this, $method))
            throw new BadMethodCallException("method '$method' does not exist");
        return call_user_func_array(array($this, $method), $args);
    }
}

Note that the __call() method does not reference the class in any way so you can copy the above for each class with protected methods you want to test and just change the class declaration. You may be able to place this function in a common base class, but I haven't tried it.

请注意, __call() 方法不会以任何方式引用该类,因此您可以使用要测试的受保护方法为每个类复制上述内容,只需更改类声明即可。您也许可以将这个函数放在一个公共基类中,但我还没有尝试过。

Now the test case itself only differs in where you construct the object to be tested, swapping in ExampleExposed for Example.

现在,测试用例本身的不同之处仅在于您构造要测试的对象的位置,将 ExampleExposed 替换为 Example。

class ExampleTest extends PHPUnit_Framework_TestCase {
    function testGetMessage() {
        $fixture = new ExampleExposed();
        self::assertEquals('hello', $fixture->getMessage());
    }
}

I believe PHP 5.3 allows you to use reflection to change the accessibility of methods directly, but I assume you'd have to do so for each method individually.

我相信 PHP 5.3 允许您使用反射直接更改方法的可访问性,但我假设您必须单独为每个方法这样做。

回答by sunwukung

I'm going to throw my hat into the ring here:

我要把我的帽子扔到这里:

I've used the __call hack with mixed degrees of success. The alternative I came up with was to use the Visitor pattern:

我使用了 __call hack 并取得了不同程度的成功。我想出的替代方案是使用访问者模式:

1: generate a stdClass or custom class (to enforce type)

1:生成 std​​Class 或自定义类(强制类型)

2: prime that with the required method and arguments

2:用所需的方法和参数来填充

3: ensure that your SUT has an acceptVisitor method which will execute the method with the arguments specified in the visiting class

3:确保您的 SUT 有一个 acceptVisitor 方法,该方法将使用访问类中指定的参数执行该方法

4: inject it into the class you wish to test

4:注入到你要测试的类中

5: SUT injects the result of operation into the visitor

5:SUT将操作的结果注入到访问者中

6: apply your test conditions to the Visitor's result attribute

6:将您的测试条件应用于访问者的结果属性

回答by Anirudh Zala

I suggest following workaround for "Henrik Paul"'s workaround/idea :)

我建议遵循“Henrik Paul”的解决方法/想法的解决方法:)

You know names of private methods of your class. For example they are like _add(), _edit(), _delete() etc.

你知道你的类的私有方法的名称。例如,它们就像 _add()、_edit()、_delete() 等。

Hence when you want to test it from aspect of unit-testing, just call private methods by prefixing and/or suffixing some commonword (for example _addPhpunit) so that when __call() method is called (since method _addPhpunit() doesn't exist) of owner class, you just put necessary code in __call() method to remove prefixed/suffixed word/s (Phpunit) and then to call that deduced private method from there. This is another good use of magic methods.

因此,当您想从单元测试方面对其进行测试时,只需通过前缀和/或后缀一些常用词(例如 _addPhpunit)来调用私有方法,以便在调用 __call() 方法时(因为方法 _addPhpunit() 不会存在)的所有者类,您只需将必要的代码放入 __call() 方法中以删除前缀/后缀词/s(Phpunit),然后从那里调用推导出的私有方法。这是魔术方法的另一个很好的用途。

Try it out.

试试看。