私は現在PHPUnitを使用して、作成しているものと一緒にテストを開発しようとしていますが、現在セッションマネージャーの作成に取り組んでおり、問題が発生しています...
セッション処理クラスのコンストラクターは次のとおりです。
private function __construct()
{
if (!headers_sent())
{
session_start();
self::$session_id = session_id();
}
}
ただし、PHPUnitはテストを開始する前にテキストを送信するため、HTTP「ヘッダー」が送信されているため、このオブジェクトでのテストは失敗したテストを返します...
さて、あなたのセッションマネージャは基本的に設計上壊れています。何かをテストできるようにするには、副作用からそれを分離することが可能でなければなりません。残念ながら、PHPは、グローバル状態の自由な使用を促進するように設計されています(echo
、header
、exit
、session_start
など)。
あなたができる最善のことは、実行時に交換できるコンポーネントの副作用を分離することです。そうすれば、テストではモックオブジェクトを使用できますが、ライブコードでは実際の副作用のあるアダプターを使用できます。これは、使用していると思われるシングルトンではうまく機能しないことがわかります。したがって、共有オブジェクトをコードに配布するには、他のメカニズムを使用する必要があります。静的レジストリから始めることもできますが、少し学習してもかまわない場合は、さらに優れたソリューションがあります。
それができない場合は、統合テストを作成するオプションが常にあります。例えば。 PHPUnitに相当する WebTestCase
を使用します。
Phpunit用のbootstrapファイルを作成します。これは以下を呼び出します:
session_start();
次に、次のようにphpunitを起動します。
phpunit --bootstrap pathToBootstrap.php --anotherSwitch /your/test/path/
bootstrapファイルは他のすべての前に呼び出されるため、ヘッダーは送信されておらず、すべてが正常に機能するはずです。
phpUnitは、テストの実行時に出力を出力するため、最初のテストでもheaders_sent()がtrueを返します。
テストスイート全体でこの問題を解決するには、セットアップスクリプトでob_start()を使用するだけです。
たとえば、phpUnitによって最初にロードされるAllTests.phpという名前のファイルがあるとします。そのスクリプトは次のようになります。
<?php
ob_start();
require_once 'YourFramework/AllTests.php';
class AllTests {
public static function suite() {
$suite = new PHPUnit_Framework_TestSuite('YourFramework');
$suite->addTest(YourFramework_AllTests::suite());
return $suite;
}
}
私は同じ問題を抱えていて、次のように--stderrフラグを指定してphpunitを呼び出すことで解決しました。
phpunit --stderr /path/to/your/test
それが誰かを助けることを願っています!
「正しい」解決策は、PHPのセッション関連関数のラッパーである非常に単純なクラス(テストする必要がないほど単純)を作成し、session_start()
を呼び出す代わりにそれを使用することだと思います。など直接。
テストパスでは、実際のステートフルでテスト不可能なセッションクラスの代わりにモックオブジェクトを使用します。
private function __construct(SessionWrapper $wrapper)
{
if (!$wrapper->headers_sent())
{
$wrapper->session_start();
$this->session_id = $wrapper->session_id();
}
}
なぜ誰もXDebugオプションをリストしていないのか疑問に思います:
/**
* @runInSeparateProcess
* @requires extension xdebug
*/
public function testGivenHeaderIsIncludedIntoResponse()
{
$customHeaderName = 'foo';
$customHeaderValue = 'bar';
// Here execute the code which is supposed to set headers
// ...
$expectedHeader = $customHeaderName . ': ' . $customHeaderValue;
$headers = xdebug_get_headers();
$this->assertContains($expectedHeader, $headers);
}
コードをテストできるように、セッションを挿入する必要があるようです。私が使用した最良のオプションは、認証プロセスにAura.Authを使用し、テストにNullSessionとNullSegmentを使用することです。
Auraフレームワークは美しく記述されており、他のAuraフレームワークに依存することなくAura.Authを単独で使用できます。
テストを開始する前に出力バッファリングを使用できませんか?出力されるすべてのものをバッファリングする場合、その時点ではまだ出力がクライアントに送信されていないため、ヘッダーの設定に問題はありません。
OBがクラス内のどこかで使用されている場合でも、それはスタック可能であり、OBは内部で何が起こっているかに影響を与えるべきではありません。
私は今bootstrapをユニットテストしているので(そうです、ほとんどの人はそうしないことを知っています)、同じ問題(header()とsession_start()の両方)に遭遇しています。 )私が見つけた解決策はかなり単純です。ユニットテストでbootstrap定数を定義し、ヘッダーを送信する前、またはセッションを開始する前にそれを確認するだけです。
// phpunit_bootstrap.php
define('UNITTEST_RUNNING', true);
// bootstrap.php (application bootstrap)
defined('UNITTEST_RUNNING') || define('UNITTEST_RUNNING', false);
.....
if(UNITTEST_RUNNING===false){
session_start();
}
これは設計上完全ではないことに同意しますが、既存のアプリケーションを単体テストしているため、大きなパーツを書き直すことは望ましくありません。また、同じロジックを使用して、__ call()および__set()マジックメソッドを使用してプライベートメソッドをテストします。
public function __set($name, $value){
if(UNITTEST_RUNNING===true){
$name='_' . $name;
$this->$name=$value;
}
throw new Exception('__set() can only be used when unittesting!');
}
bootstrapファイルの作成は、4つの投稿が戻ってきたことを指摘し、これを回避する最もクリーンな方法のようです。
多くの場合、PHPを維持し、非常に小さくまとめられたレガシープロジェクトに何らかのエンジニアリング規律を追加しようとします。私たちには捨てる時間(または権限)がありませんゴミの山全体をやり直してください。そのため、troelsknによる最初の回答が常に可能であるとは限りません(最初の設計に戻ることができれば、PHP全体として、Web開発の世界でこのCOBOLを永続化するのを助けるのではなく、Rubyまたはpythonなどのより現代的なものを使用してください。)
それら全体でsession_startまたはsetcookieを使用するモジュールの単体テストを作成しようとしている場合は、ブーストラップファイルでセッションを開始するよりも、これらの問題を回避できます。