web-dev-qa-db-ja.com

フックのコールバックをテストする

私はTDDを使用してプラグインを開発していますが、テストに失敗したことの1つは...フックです。

私は大丈夫、私はフックコールバックをテストすることができますが、フックが実際に起動するかどうかどうすればテストできますか(カスタムフックとWordPressデフォルトフックの両方)?私はいくらかのモックが役立つと思います、しかし私は単に私が欠けているものを理解することができません。

私はWP-CLIでテストスイートをインストールしました。 この答えによるとinitフックは起動するはずですが、そうではありません。また、コードはWordPress内で機能します。

私の理解するところでは、ブートストラップは最後にロードされるので、initを起動しないことは一種の理にかなっています。それで、残る問題は次のとおりです。

ありがとうございます。

ブートストラップファイルは次のようになります。

$_tests_dir = getenv('WP_TESTS_DIR');
if ( !$_tests_dir ) $_tests_dir = '/tmp/wordpress-tests-lib';

require_once $_tests_dir . '/includes/functions.php';

function _manually_load_plugin() {
  require dirname( __FILE__ ) . '/../includes/RegisterCustomPostType.php';
}
tests_add_filter( 'muplugins_loaded', '_manually_load_plugin' );

require $_tests_dir . '/includes/bootstrap.php';

テスト済みファイルは次のようになります。

class RegisterCustomPostType {
  function __construct()
  {
    add_action( 'init', array( $this, 'register_post_type' ) );
  }

  public function register_post_type()
  {
    register_post_type( 'foo' );
  }
}

そしてテスト自体:

class CustomPostTypes extends WP_UnitTestCase {
  function test_custom_post_type_creation()
  {
    $this->assertTrue( post_type_exists( 'foo' ) );
  }
}

ありがとうございます。

31
Ionut Staicu

単独でテストする

プラグインを開発する場合、プラグインをテストする最良の方法は、WordPress環境をロードすることなくwithoutです。

WordPressなしで簡単にテストできるコードを作成すると、コードはbetterになります。

単体テストされるすべてのコンポーネントは、isolationでテストする必要があります。クラスをテストするときは、他のすべてのコードを想定して、その特定のクラスのみをテストする必要があります完璧に機能しています。

The Isolator

これが、単体テストが「ユニット」と呼ばれる理由です。

追加の利点として、コアをロードしなくても、テストがはるかに高速に実行されます。

コンストラクターでのフックを避ける

私があなたに与えることができるヒントは、コンストラクターにフックを置くことを避けることです。これは、コードを単独でテスト可能にすることの1つです。

OPでテストコードを見てみましょう。

class CustomPostTypes extends WP_UnitTestCase {
  function test_custom_post_type_creation() {
    $this->assertTrue( post_type_exists( 'foo' ) );
  }
}

そして、このテストfailsを想定してみましょう。 犯人は誰ですか?

  • フックがまったく追加されていないか、適切に追加されていませんか?
  • 投稿タイプを登録するメソッドがまったく呼び出されなかったか、引数が間違っていませんか?
  • wordPressにバグがありますか?

どのように改善できますか?

クラスコードが次のとおりであるとします。

class RegisterCustomPostType {

  function init() {
    add_action( 'init', array( $this, 'register_post_type' ) );
  }

  public function register_post_type() {
    register_post_type( 'foo' );
  }
}

(注:残りの回答については、このバージョンのクラスを参照します)

このクラスを記述した方法では、add_actionを呼び出さずにクラスのインスタンスを作成できます。

上記のクラスには、テストするものが2つあります。

  • メソッドinitactuallyadd_actionを呼び出し、適切な引数を渡します
  • メソッドregister_post_type実際にregister_post_type関数を呼び出す

投稿タイプが存在するかどうかを確認する必要があるとは言いませんでした。適切なアクションを追加し、register_post_typeを呼び出すと、カスタム投稿タイプmust存在する:存在しない場合は、WordPressの問題です。

注意:プラグインをテストするときは、WordPressコードではなく、yourコードをテストする必要があります。テストでは、WordPress(使用する他の外部ライブラリと同様)がうまく機能することを前提とする必要があります。それがunitテストの意味です。

しかし...実際には?

WordPressがロードされていない場合、上記のクラスメソッドを呼び出そうとすると致命的なエラーが発生するため、関数をモックする必要があります。

「手動」メソッド

モックライブラリを作成することも、すべてのメソッドを「手動で」モックすることもできます。それが可能だ。その方法を説明しますが、簡単な方法を紹介します。

テストの実行中にWordPressがロードされない場合、その機能を再定義できることを意味します。 add_actionまたはregister_post_type

bootstrapファイルからロードされたファイルがあるとします。

function add_action() {
  global $counter;
  if ( ! isset($counter['add_action']) ) {
    $counter['add_action'] = array();
  }
  $counter['add_action'][] = func_get_args();
}

function register_post_type() {
  global $counter;
  if ( ! isset($counter['register_post_type']) ) {
    $counter['register_post_type'] = array();
  }
  $counter['register_post_type'][] = func_get_args();
}

関数が呼び出されるたびに、単純にグローバル配列に要素を追加するように関数を書き直しました。

ここで、PHPUnit_Framework_TestCase:を拡張する独自のベーステストケースクラスを作成する必要があります(まだない場合)。これにより、テストを簡単に構成できます。

次のようになります。

class Custom_TestCase extends \PHPUnit_Framework_TestCase {

    public function setUp() {
        $GLOBALS['counter'] = array();
    }

}

この方法では、すべてのテストの前に、グローバルカウンターがリセットされます。

そして今、あなたのテストコード(私は上記で投稿したrewrittenクラスを参照します):

class CustomPostTypes extends Custom_TestCase {

  function test_init() {
     global $counter;
     $r = new RegisterCustomPostType;
     $r->init();
     $this->assertSame(
       $counter['add_action'][0],
       array( 'init', array( $r, 'register_post_type' ) )
     );
  }

  function test_register_post_type() {
     global $counter;
     $r = new RegisterCustomPostType;
     $r->register_post_type();
     $this->assertSame( $counter['register_post_type'][0], array( 'foo' ) );
  }

}

次のことに注意してください。

  • 2つのメソッドを別々に呼び出すことができ、WordPressはまったくロードされません。この方法で、1つのテストが失敗した場合、犯人が誰であるか正確にわかります。
  • 先ほど言ったように、ここではクラスがWP関数を予想される引数で呼び出すことをテストします。 CPTが実際に存在するかどうかをテストする必要はありません。 CPTの存在をテストしている場合、プラグインの動作ではなく、WordPressの動作をテストしています...

素敵な..しかし、それはピタです!

はい、すべてのWordPress関数を手動でモックする必要がある場合は、本当に苦痛です。私ができるいくつかの一般的なアドバイスは、できるだけ少ないWP関数を使用することです:WordPressをrewriteする必要はありませんが、abstractWPカスタムクラスで使用する関数。これにより、モックを作成して簡単にテストできます。

例えば。上記の例に関しては、投稿タイプを登録するクラスを作成し、指定された引数で「init」でregister_post_typeを呼び出すことができます。この抽象化では、まだそのクラスをテストする必要がありますが、投稿タイプを登録するコードの他の場所では、そのクラスを使用して、テストでモックすることができます(したがって、動作すると仮定します)。

素晴らしいことは、CPT登録を抽象化するクラスを作成する場合、そのための個別のリポジトリを作成でき、 Composer などの最新のツールのおかげで、必要なすべてのプロジェクトにそれを埋め込むことができます:一度テストし、everywhereを使用します。また、バグを見つけた場合は、1か所で修正することができ、単純なcomposer updateを使用して、使用されているすべてのプロジェクトも修正されます。

2回目:単独でテスト可能なコードを記述することは、より良いコードを記述することを意味します。

しかし、遅かれ早かれ、どこかでWP関数を使用する必要があります...

もちろん。コアと並行して行動するべきではありません、意味がありません。 WP関数をラップするクラスを作成できますが、それらのクラスもテストする必要があります。上記の「手動」メソッドは、非常に単純なタスクに使用できますが、クラスに多くのWP関数が含まれている場合、苦労する場合があります。

幸いなことに、そこには良いことを書く良い人がいます。 - 10up、最大のWP代理店の1つは、プラグインを正しい方法でテストします。 WP_Mock です。

モックWP関数をフックにすることができます。テストにロードしたと仮定すると(レポREADMEを参照)、上で書いたものと同じテストがなります:

class CustomPostTypes extends Custom_TestCase {

  function test_init() {
     $r = new RegisterCustomPostType;
     // tests that the action was added with given arguments
     \WP_Mock::expectActionAdded( 'init', array( $r, 'register_post_type' ) );
     $r->init();
  }

  function test_register_post_type() {
     // tests that the function was called with given arguments and run once
     \WP_Mock::wpFunction( 'register_post_type', array(
        'times' => 1,
        'args' => array( 'foo' ),
     ) );
     $r = new RegisterCustomPostType;
     $r->register_post_type();
  }

}

簡単ですね。この答えはWP_Mockのチュートリアルではないので、詳細についてはリポジトリのreadmeを読んでください。しかし、上記の例はかなり明確なはずです。

さらに、モックされたadd_actionregister_post_typeを自分で書いたり、グローバル変数を維持したりする必要はありません。

そしてWPクラス?

WPにはいくつかのクラスもあり、テストの実行時にWordPressがロードされない場合、それらをモックする必要があります。

これは関数をモックするよりもはるかに簡単です。PHPUnitにはオブジェクトをモックする組み込みシステムがありますが、ここでは Mockery を提案します。これは非常に強力なライブラリであり、非常に使いやすいです。さらに、これはWP_Mockの依存関係であるため、持っている場合はMockeryもあります。

しかし、WP_UnitTestCaseはどうですか?

WordPressテストスイートは、WordPresscoreをテストするために作成され、コアに貢献したい場合これは極めて重要ですが、プラグインにそれを使用することで、単独ではなくテストすることができます。

WPの世界に目を向けてください。多くの最新のPHPフレームワークとCMSがあり、プラグイン/モジュール/拡張機能(またはそれらが呼び出されるもの)をテストすることを提案するものはありませんフレームワークコード。

スイートの便利な機能である工場を見逃した場合、 awesome things があることを知っておく必要があります。

落とし穴と欠点

ここで提案したワークフローに欠けている場合があります:カスタムデータベーステスト

実際、標準のWordPressテーブルと関数を使用して(最低レベルの$wpdbメソッドで)そこに書き込む場合、実際にデータを書き込む必要はありませんデータベース内のデータは実際にはです。適切な引数で適切なメソッドが呼び出されることを確認してください。

ただし、カスタムテーブルとプラグインを作成して、そこに書き込むクエリを作成し、それらのクエリが機能するかどうかをテストすることができます。

そのような場合、WordPressテストスイートは大いに役立ちます。また、dbDeltaなどの関数を実行するために、WordPressのロードが必要になる場合があります。

(テストに別のデータベースを使用する必要はありませんよね?)

幸いなことに、PHPUnitを使用すると、個別に実行できる「スイート」でテストを整理できるため、WordPress環境(またはその一部)をロードして残りのすべてを残すカスタムデータベーステスト用のスイートを作成できますテストWordPress-free

モックを使用すると、データベースを処理せずに大部分のクラスを適切にテストできるように、できるだけ多くのデータベース操作を抽象化するクラスを作成してください。

第三に、単独で簡単にテスト可能なコードを書くことは、より良いコードを書くことを意味します。

67
gmazzap