7

PHP セッションを操作および管理するための API を提供するモジュールを実装しています。Session\Managerユーザーがセッションを開始したり、ID を設定したり、ID を取得したり、セッションを破棄したりできるようにする実装をテストしています。PHPUnit の@runInSeparateProcessアノテーションを使用して、このクラスのメソッドを別のプロセスでテストしています。このアノテーションを使用すると、非シリアル化エラーが原因で PHPUnit によって例外がスローされます。注釈を使用しないと、テストは期待どおりに実行され、null が false に等しくない場合に失敗します。

エラーの原因となったテストは次のとおりです。これまでのところ、実装の詳細は作成されていません。インターフェイスのすべてのメソッドが存在しますが、操作は実行されません。

class ManagerTest extends PHPUnitTestCase {

    /**
     * Ensures that if the session has not been started yet the sessionExists
     * method returns false.
     *
     * @runInSeparateProcess
     */
    public function testSessionExistsWhenSessionHasNotBeenStarted() {
        $Manager = new \Session\Manager();
        $this->assertFalse($Manager->sessionExists());
    }

}

PHPUnit_Util_PHP::runJob()問題を次の方法までたどることができました。PHPUnit 3.7.5 を実行しており、runJob呼び出されるメソッドは次のとおりです。

/**
 * Runs a single job (PHP code) using a separate PHP process.
 *
 * @param  string                       $job
 * @param  PHPUnit_Framework_TestCase   $test
 * @param  PHPUnit_Framework_TestResult $result
 * @return array|null
 * @throws PHPUnit_Framework_Exception
 */
public function runJob($job, PHPUnit_Framework_Test $test = NULL, PHPUnit_Framework_TestResult $result = NULL)
{
    $process = proc_open(
      $this->getPhpBinary(),
      array(
        0 => array('pipe', 'r'),
        1 => array('pipe', 'w'),
        2 => array('pipe', 'w')
      ),
      $pipes
    );

    if (!is_resource($process)) {
        throw new PHPUnit_Framework_Exception(
          'Unable to create process for process isolation.'
        );
    }

    if ($result !== NULL) {
        $result->startTest($test);
    }

    $this->process($pipes[0], $job);
    fclose($pipes[0]);

    $stdout = stream_get_contents($pipes[1]);
    fclose($pipes[1]);

    $stderr = stream_get_contents($pipes[2]);
    fclose($pipes[2]);

    proc_close($process);
    $this->cleanup();

    if ($result !== NULL) {
        $this->processChildResult($test, $result, $stdout, $stderr);
    } else {
        return array('stdout' => $stdout, 'stderr' => $stderr);
    }
}

この行は、 の長い文字列と等しくなり$stdout = stream_get_contents($pipes[1]);ます。の値はシリアル化されておらず、この関数に無効な値が渡されると警告がトリガーされ、例外がスローされます。の戻り値が であることも確認できました。$stdout?$this->processChildResult$stdout$this->getPhpBinary()/usr/bin/php

スローされる例外メッセージ:

PHPUnit_Framework_Exception: ???...???"*???...??


Caused by
ErrorException: unserialize(): Error at offset 0 of 10081 bytes

hek2mgl のおかげで、 $job の var_dump の出力を保持するこの要点で$jobPHP コードを確認できます。かなりの量のコードであり、この質問はすでにかなり長いため、リンクを作成しました。

この特定のドメインに関する知識は限界に達しており、この問題をさらにデバッグする方法がわかりません。@runInSeparateProcessが失敗する理由と$stdout、別のプロセスを実行すると ? の長い文字列が表示される理由がわかりません。マーク。この問題の原因は何ですか?どうすれば解決できますか? セッションの開始と破棄がテストに影響を与えないようにするために、将来のテストでは別のプロセスで実行する必要があるため、このモジュールについては行き詰まっています。

  • PHP: 5.3.15
  • PHPユニット: 3.7.5
  • IDE およびコマンド ライン テスト ランナーで発生したエラー
4

4 に答える 4

9

わかりました、私はここで何が起こっているのかを理解しました。

テスト中、フレームワークはグローバルな状態を維持しようとします。このPHPUnit_Framework_TestCase::run()関数では、現在のグローバルが によって PHP コードの文字列に変換されますPHPUnit_Util_GlobalState::getGlobalsAsString()。現在、適切なクラス ファイルの要求に対応するために、オートローディング ライブラリを使用しています。$GLOBALSこのクラスは、グローバル スコープ内の変数で宣言されたため、そのオブジェクトのシリアル化がによって作成された配列に入れられましたgetGlobalsAsString()。このシリアル化には、何らかの理由で null バイト文字が含まれています\u0000。これをシリアル化解除しようとすると、エラーが発生し、最終的に出力も破損します。

この問題を修正するには、テスト ブートストラップで関数を作成し、グローバル スコープの外にオートローディング オブジェクトを作成します。スクリプトがヌル バイトのシリアル化解除を試行しなくなったため、スクリプトはエラーを生成せず、テスト ケースは期待どおりに動作します。これは本当にファンキーな問題です。私のコードのエラーなのか、PHPUnit のコードなのか、serialize関数の内部エラーなのかわかりません。


または、テスト ケースで関数をオーバーライドし、run()グローバル状態を保持しないようにテスト ケースを明示的に設定することもできます。デフォルトの動作のように、含まれているすべてのファイルを再度要求するという他の問題に簡単に遭遇する可能性があるため、これはおそらくよりクリーンなソリューションです。

class ManagerTest extends PHPUnit_Framework_TestCase {

    public function run(PHPUnit_Framework_TestResult $result = null) {
        $this->setPreserveGlobalState(false);
        parent::run($result);
    }

}

関数が呼び出される前に、グローバル状態を設定する必要があります。PHPUnit_Framework_TestCase::run()これは、グローバル状態の保存を適切に確実に設定する唯一の方法です。

于 2013-01-25T03:13:22.773 に答える
2

$job 引数PHPUnit_Util_PHP::runJob()は、コマンドライン スクリプトとして実行できる方法でテスト メソッドのコードをラップする、PHPUnit によって生成される php コードです (巧妙なトリックです!)。分離されたテストは、テスト結果に関するシリアル化された php データを phpunit に出力します。

次の行を最上位 pf に追加すると、このコードを調査できます。PHPUnit_Util_PHP::runJob()

file_put_contents('isolated.php', $job);

失敗したテストを再度実行するisolated.phpと、現在のディレクトリにファイルが作成されているはずです。次のように入力して、コードを(現在のフォルダーで)実行できるコードを確認できます

php isolated.php

乱雑に見える出力が表示されますか?

また、メソッドをデバッグする必要がありますPHPUnit_Util_PHP::processChildResult()。このメソッドは、テスト結果のデシリアライズとチェックを担当します。そこにいくつかのエコーまたは var_dumps を配置するか、デバッガーを使用します。このメソッドを見ると、テスト メソッドからの不正な出力が問題の原因である可能性が非常に高くなります。

于 2013-01-24T02:53:48.430 に答える
1

php.ini で設定してみてください:

detect_unicode = オフ

私のために働いた。

于 2013-03-18T11:35:18.203 に答える