ヘッダーを送信する必要があるアイテムを使用した単体テスト
-
06-07-2019 - |
質問
現在、PHPUnitと協力して、書いているものと並行してテストを開発しようとしていますが、現在Session Managerの作成に取り組んでおり、そのための問題が発生しています...
セッション処理クラスのコンストラクターは
private function __construct()
{
if (!headers_sent())
{
session_start();
self::$session_id = session_id();
}
}
ただし、PHPUnitはテストを開始する前にテキストを送信するため、このオブジェクトに対するテストは失敗したテストを返します。これは、HTTP" Headers"送信されました...
解決
まあ、セッションマネージャーは基本的に設計上壊れています。何かをテストできるようにするには、副作用から隔離できる必要があります。残念ながら、PHPはグローバルな状態( echo
、 header
、 exit
、 session_startを自由に使用できるように設計されています
など)。
できる最善のことは、コンポーネントを実行時に交換できる副作用を分離することです。そのようにして、テストではモックされたオブジェクトを使用できますが、ライブコードでは実際の副作用があるアダプターを使用します。これはシングルトンではうまく動作しないことがわかりますが、これは使用していると思われます。そのため、共有オブジェクトをコードに配布するには、他のメカニズムを使用する必要があります。静的レジストリから始めることもできますが、少し学習してもかまわない場合は、さらに優れたソリューションがあります。
それができない場合は、常に統合テストを作成するオプションがあります。例えば。 WebTestCase
に相当するPHPUnitを使用します。
他のヒント
次を呼び出すphpunitのブートストラップファイルを作成します。
session_start();
次にphpunitを次のように起動します:
phpunit --bootstrap pathToBootstrap.php --anotherSwitch /your/test/path/
ブートストラップファイルは他のすべての前に呼び出されるため、ヘッダーは送信されず、すべて正常に動作します。
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);
}
テストを開始する前に出力バッファリングを使用できませんか?出力されるものをすべてバッファリングする場合、その時点ではまだクライアントに出力が送信されていないため、ヘッダーの設定に問題はないはずです。
OBがクラス内のどこかで使用されていても、スタック可能であり、OBは内部で行われていることに影響を与えません。
私が知る限り、Zend FrameworkはZend_Sessionパッケージのテストに同じ出力バッファリングを使用しています。テストケースを見て、始めましょう。
4つの投稿が指摘されたブートストラップファイルの作成が、これを回避する最もクリーンな方法のようです。
多くの場合、PHPを使用して保守しなければならず、ある種のエンジニアリングの規律を、非常に小さくまとめられたレガシープロジェクトに追加しようとします。ゴミの山全体を捨てて再びやり直す時間(または権限)がないため、troelsknによる最初の回答は、常に前進できるとは限りません。 (初期設計に戻ることができれば、PHPを捨てて、Web開発の世界のこのCOBOLを永続させるのではなく、rubyやpythonなどのより新しいものを使用できます。)
session_startまたはsetcookieを使用するモジュールの単体テストを記述しようとしている場合、boostrapファイルでセッションを開始すると、これらの問題が発生します。
今、ブートストラップのユニットテストを行っているので(ほとんどの人がそうしないことを知っています)、同じ問題(header()とsession_start()の両方)に直面しています。 私が見つけた解決策はかなり単純です。ユニットテストのブートストラップで定数を定義し、ヘッダーを送信する前またはセッションを開始する前にそれを確認するだけです:
// 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!');
}
コードをテストするには、セッションを挿入する必要があるようです。私が使用した最良のオプションは、認証プロセスにAura.Authを使用し、テストにNullSessionとNullSegmentを使用することです。
Auraフレームワークは美しく書かれており、他のAuraフレームワークの依存関係なしにAura.Authを単独で使用できます。