324

Do you test private methodに関する議論は参考になりました。

一部のクラスでは、メソッドを保護したいが、それらをテストすることにしました。これらのメソッドの一部は、静的で短いものです。パブリック メソッドのほとんどはそれらを使用するため、後でテストを安全に削除できる可能性があります。しかし、TDD アプローチから始めてデバッグを避けるために、私は本当にそれらをテストしたいと思っています。

私は次のことを考えました:

  • 回答でアドバイスされているメソッドオブジェクトは、これにはやり過ぎのようです。
  • パブリック メソッドから始めて、より高いレベルのテストによってコード カバレッジが提供されたら、それらを保護してテストを削除します。
  • 保護されたメソッドを公開するテスト可能なインターフェースを持つクラスを継承する

ベストプラクティスはどれですか? 他に何かありますか?

JUnit は自動的に保護されたメソッドを public に変更するようですが、私はそれを詳しく見ていませんでした。PHP では、これをリフレクション経由で行うことはできません。

4

10 に答える 10

475

PHPUnit で PHP5 (>= 5.3.2) を使用している場合は、テストを実行する前に、リフレクションを使用してプライベート メソッドと保護されたメソッドをパブリックに設定することでテストできます。

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(...));
  ...
}
于 2010-05-09T15:53:38.300 に答える
55

ティーストバーンには正しいアプローチがあります。さらに簡単なのは、メソッドを直接呼び出して答えを返すことです。

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

これは、テストで次のように簡単に呼び出すことができます。

$returnVal = PHPUnitUtil::callMethod(
                $this->object,
                '_nameOfProtectedMethod', 
                array($arg1, $arg2)
             );
于 2012-01-02T15:06:37.563 に答える
54

あなたはすでに気づいているようですが、とにかくもう一度言います。保護されたメソッドをテストする必要がある場合、これは悪い兆候です。単体テストの目的は、クラスのインターフェースをテストすることであり、保護されたメソッドは実装の詳細です。そうは言っても、それが理にかなっている場合もあります。継承を使用する場合、スーパークラスがサブクラスのインターフェイスを提供していると見なすことができます。したがって、ここでは、保護されたメソッドをテストする必要があります (ただし、プライベートメソッドはテストしないでください)。これに対する解決策は、テスト目的でサブクラスを作成し、これを使用してメソッドを公開することです。例えば。:

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

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

継承はいつでも構成に置き換えることができることに注意してください。コードをテストするときは、通常、このパターンを使用するコードを扱う方がはるかに簡単であるため、そのオプションを検討することをお勧めします。

于 2008-10-30T10:46:19.207 に答える
24

uckelman's answerで定義されている getMethod() にわずかなバリエーションを提案したいと思います。

このバージョンでは getMethod() が変更され、ハードコードされた値が削除され、使用法が少し簡素化されています。以下の例のように PHPUnitUtil クラスに追加するか、PHPUnit_Framework_TestCase 拡張クラス (または、PHPUnitUtil ファイルにグローバルに追加することをお勧めします) に追加することをお勧めします。

とにかく 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
}

また、エイリアス関数 getProtectedMethod() を作成して、何が期待されるかを明示しましたが、それはあなた次第です。

于 2011-04-15T01:37:38.367 に答える
12

troelsknは近いと思います。代わりにこれを行います:

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

次に、次のようなものを実装します。

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

次に、TestClassToTest に対してテストを実行します。

コードを解析することにより、そのような拡張クラスを自動的に生成できるはずです。PHPUnit が既にそのようなメカニズムを提供していても驚かないでしょう (確認はしていませんが)。

于 2008-10-31T18:36:25.370 に答える
5

実際、__ call()を一般的な方法で使用して、保護されたメソッドにアクセスできます。このクラスをテストできるようにするには

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

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

__call()メソッドはクラスをまったく参照しないため、テストする保護されたメソッドを使用してクラスごとに上記をコピーし、クラス宣言を変更するだけでよいことに注意してください。この関数を共通の基本クラスに配置できるかもしれませんが、私は試していません。

これで、テストケース自体は、テスト対象のオブジェクトを作成する場所のみが異なり、ExampleExposedをExampleに交換します。

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

PHP 5.3では、リフレクションを使用してメソッドのアクセシビリティを直接変更できると思いますが、メソッドごとに個別に変更する必要があると思います。

于 2010-05-07T18:36:20.610 に答える
5

ここでリングに帽子を投げます。

私は __call ハックを使用しましたが、成功の度合いはさまざまです。私が思いついた別の方法は、Visitor パターンを使用することでした。

1: stdClass またはカスタム クラスを生成する (型を強制するため)

2: 必要なメソッドと引数でプライムする

3: SUT に、訪問中のクラスで指定された引数を使用してメソッドを実行する acceptVisitor メソッドがあることを確認してください。

4: テストしたいクラスに注入する

5: SUT は操作の結果をビジターに注入します

6: テスト条件を Visitor の結果属性に適用する

于 2010-08-26T15:59:45.047 に答える
2

「Henrik Paul」の回避策/アイデアの次の回避策をお勧めします:)

クラスのプライベート メソッドの名前を知っている。たとえば、_add()、_edit()、_delete() などです。

したがって、単体テストの観点からテストしたい場合は、 __call() メソッドが呼び出されたときに (メソッド_addPhpunit () が存在する) 所有者クラスの場合、必要なコードを __call() メソッドに配置して、接頭辞/接尾辞付きの単語 (Phpunit) を削除し、そこから推定されたプライベート メソッドを呼び出します。これは、魔法のメソッドのもう 1 つの有効な使い方です。

やってみて。

于 2009-09-09T10:32:34.963 に答える