php - あるクラスが別のクラスに依存しているテストファイルを再編成するためのオプション?

php phpunit

従来のphpプロジェクトのテストケースを再編成してスイートをテストしているときに、別のクラスに依存するクラスをテストする問題に遭遇しました。以下は、testsフォルダの元のディレクトリレイアウトです。

tests/
   |  
   +- models/
   |     | ClassATest.php
   |     | ClassBTest.php
   |     | ...
   +- controllers/
   |     | ...


現在、テストスイートはありません。各テストは個別に実行されます。

これらのテストを再編成するために、最初に、phpunit.xmlを作成し、以下のようなスイートにテストをグループ化します。

phpunit.xml

<phpunit bootstrap="...">
    <testsuite name="Models">
        <directory>./models</directory>
    </testsuite>
    ...
</phpunit>


今私はトラブルに巻き込まれます。 ClassAとClassBの2つのモデルがあり、ClassBのメソッドはClassAに依存しています。

class ClassA
{
    public function getValue(...)
    {
        ...
    }
}

class ClassB
{
    public function doSomething()
    {
        $a = new ClassA();
        $x = $a->getValue(...);
        // do something with $x
        ...
    }
 }


ここで、テストに関しては誰もが問題を認識していると思います。 ClassA::getValue()をモックするために、ClassBTest.phpは次のようにClassAを(再)定義しました。

ClassBTest.php

class ClassA
{
    public function getValue(...)
    {
        return 'a mock value';
    }
}

class ClassBTest extends PHPUnit_Framework_TestCase
{
    public function testDoSomething()
    {
        $b = new ClassB();
        // Make an assertion with $b->doSomething()
    }
}


したがって、各モデルを個別にテストすると機能します。しかし、スイートで実行すると、実行中のプロセスのある時点でClassAが再宣言され、phpunitが終了するため、エラーが発生します。

私の質問は、この状況にどう対処するかです。私はいくつかのオプションを試しました。


テストファイルで再定義されたクラスを削除し、依存関係注入を使用します。依存関係注入のためにモデルクラスを変更する必要がないため、この方法は推奨されません。
グループ注釈を使用して、ClassBTest.phpなどのテストファイルを除外し、除外されたファイルを個別に実行します。このオプションは便利ではありません。


プロセスでファイルのグループを実行し、別のプロセスでファイルの別のグループを実行するようにphpunitを構成できますか? PHPは実行時にクラスを動的にロードおよびアンロードできますか?他にオプションがある場合は、提案してください。

ありがとう!
答え
PHPは実行時にクラスを動的にロードおよびアンロードできますか?他にオプションがある場合は、提案してください。


はい。これは、リファクタリングできないコードをテストする最後の手段です。 the runkit extension, method_renameを見てください。 「模擬」したい元のメソッドの名前を変更できます。モック関数を作成し、テスト後に名前を変更します。

新しいphp test helpersは名前変更機能も提供します。



または、"runInIsolation" / "process-isolation"を使用できます。すべてのテストケースは、別のPHPプロセスで実行されます。テストケースでこれらのクラスのみを定義すると、「定義済み」の問題が発生することはありません。



拡張機能に依存しない、またはテストスイートを遅くする多くのphpプロセスを生成しない3番目のオプション

それは少しハッキリに見えるかもしれませんが、すべてのオプションを提供するだけです:

class ClassB
{
    public function doSomething()
    {
        $a = new ClassA();
        $x = $a->getValue(...);
        // do something with $x
        ...
    }
 }


多くのリファクタリングをしなくても、次のことができます:

クラスClassB
{
パブリック関数doSomething()
{
$ a = $ this-> getClassA();
$ x = $ a-> getValue(...);
// $ xで何かします
...
}

protected function getClassA() {
    if(!$this->classA) { 
        return new ClassA(); 
    } 
    return $this->classA;
}

public function setClassAForTesting(ClassA $classA) {
    $this->classA = $classA;
}


}

これにより、コードベースが "テスト用コード"で乱雑になりますが、setter injectionを使用すると、本番用コードが以前と同じように機能する一方で、依存性注入をテストに使用できます。

この方法にはいくつかの作業が必要ですが、私の経験では、PHP拡張機能を使用して動作をハッキングするよりもはるかに持続可能です。
関連記事

php - MySQLのフィールド/行がいつ正常に更新されたかを知るにはどうすればよいですか?

php - PHPでWHMのプラグインを作成することは可能ですか?

php - 動的に生成された画像をキャッシュする

php - Graph APIを使用して投稿の「タイプ」フィールドを取得する

php - アプリケーションまたはデータベースのタイムスタンプ列を更新しますか?

php - Regexを介してアンダースコアと共にUnicode文字を受け入れる

php - サーバーのパブリックフォルダーに送られるすべてのリクエストを処理する1つのPHPファイルを作成することは可能ですか?

php - PHPの型ヒントが、私のクラスを文字列であると考えるのはなぜですか?

php - mysql_connect()からの警告がmysql_error()を生成しない

php - PHP多対多セットID