web-dev-qa-db-ja.com

単体テストと静的メソッド

次の投稿 の意味を理解しようとする単体テストを読み上げて、静的関数呼び出しの困難を説明します。

私はこの問題を明確に理解していません。私は常に静的関数がクラス内のユーティリティ関数を切り上げる良い方法であると考えてきました。たとえば、静的関数呼び出しを使用して初期化することがよくあります。つまり、

Init::loadConfig('settings.php');
Init::setErrorHandler(APP_MODE); 
Init::loggingMode(APP_MODE);

// start loading app related objects ..
$app = new App();

//投稿を読んだ後、代わりにこれを目指します...

$init = new Init();
$init->loadConfig('settings.php');
$init->loggingMode(APP_MODE);
 // etc ...

しかし、このクラスのために書いた数十のテストは同じです。私は何も変えませんでした、そして、彼らはまだすべてパスします。私は何か間違っていますか?

投稿の著者は次のように述べています:

静的メソッドの基本的な問題は、手続き型コードであるということです。手続き型コードの単体テストの方法がわかりません。単体テストでは、アプリケーションの一部を分離してインスタンス化できると想定しています。インスタンス化中に、実際の依存関係を置き換えるモック/フレンドリで依存関係を配線します。手続き型プログラミングでは、オブジェクトがなく、コードとデータが分離されているため、「配線」するものは何もありません。

今、私は静的メソッドが依存関係を作成することを投稿から理解していますが、静的メソッドの戻り値を通常のメソッドと同じくらい簡単にテストできない理由を直感的に理解していないのですか?

私は静的メソッドを避けますが、静的メソッドが有用である場合のアイデアがあればいいと思います。この投稿から、静的メソッドはグローバル変数とほぼ同じくらい悪であり、可能な限り避ける必要があるようです。

この件に関する追加情報またはリンクは大歓迎です。

43
stefgosselin

静的メソッド自体は、インスタンスメソッドよりもテストが難しくありません。問題は、テスト対象のメソッドを分離できないため、メソッド(静的またはその他)がother staticメソッドを呼び出すときに発生します。以下に、テストが難しい典型的なメソッドの例を示します。

_public function findUser($id) {
    Assert::validIdentifier($id);
    Log::debug("Looking for user $id");  // writes to a file
    Database::connect();                 // needs user, password, database info and a database
    return Database::query(...);         // needs a user table with data
}
_

この方法で何をテストしたいですか?

  • 正の整数以外を渡すと、InvalidIdentifierExceptionがスローされます。
  • Database::query()は正しい識別子を受け取ります。
  • 見つかった場合は一致するUserが返され、見つからない場合はnullが返されます。

これらの要件は簡単ですが、ロギングのセットアップ、データベースへの接続、データのロードなども行う必要があります。Databaseクラスは、接続とクエリができることをテストする責任があります。 Logクラスは、ロギングに対して同じことを行う必要があります。 findUser()はこれに対処する必要はありませんが、それらに依存しているため、そうする必要があります。

代わりに、上記のメソッドがDatabaseおよびLogインスタンスのインスタンスメソッドを呼び出した場合、テストは、テストに固有のスクリプト化された戻り値を持つモックオブジェクトを渡すことができます。

_function testFindUserReturnsNullWhenNotFound() {
    $log = $this->getMock('Log');  // ignore all logging calls
    $database = $this->getMock('Database', array('connect', 'query');
    $database->expects($this->once())->method('connect');
    $database->expects($this->once())->method('query')
             ->with('<query string>', 5)
             ->will($this->returnValue(null));
    $dao = new UserDao($log, $database);
    self::assertNull($dao->findUser(5));
}
_

findUser()connect()の呼び出しを怠った場合、_$id_(上記の_5_)に間違った値を渡すか、null以外を返す場合、上記のテストは失敗します。素晴らしい点は、データベースが含まれていないため、テストが迅速かつ堅牢になり、ネットワーク障害や不良サンプルデータなど、テストに関係のない理由で失敗しないことです。本当に重要なことに焦点を当てることができます:findUser()に含まれる機能。

51
David Harkness

Sebastian BergmannはMisko Heveryに同意し、頻繁に引用しています。

単体テストには継ぎ目が必要です。継ぎ目は、通常のコードパスの実行を妨げる場所であり、テスト対象のクラスの分離を実現する方法です。シームはポリモーフィズムを介して動作し、クラス/インターフェイスをオーバーライド/実装し、テスト中のクラスを異なる方法で配線して、実行フローを制御します。静的メソッドでは、オーバーライドするものは何もありません。はい、静的メソッドは簡単に呼び出すことができますが、静的メソッドが別の静的メソッドを呼び出す場合、呼び出されたメソッドの依存関係をオーバーライドする方法はありません。

静的メソッドの主な問題は、通常、使用するコードに依存関係をハードコーディングすることにより結合を導入し、ユニットテストでスタブまたはモックに置き換えるのが困難になることです。これは Open/Closed Principle および Dependency Inversion Principle の2つの SOLID原則 に違反しています。

静電気は有害と見なされます ということは絶対に正しいです。それらを避けてください。

追加情報については、リンクを確認してください。

更新:静的変数は依然として有害であると見なされますが、 静的メソッドをスタブおよびモックする機能はPHPUnit 4.0で削除されました

21
Gordon

静的メソッドをテストするときに問題は見られません(少なくとも、非静的メソッドには存在しないものはありません)。

  • モックオブジェクトは、依存性注入を使用してテスト対象のクラスに渡されます。
  • 模擬静的メソッドは、適切なオートローダーを使用するか、include_path
  • 遅延静的バインディングは、同じクラスの静的メソッドを呼び出すメソッドを処理します。
1
Oswald