フックコールバックのテスト


34

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

つまり、フックコールバックをテストできますが、フックが実際にトリガーされるかどうかをテストするにはどうすればよいですか(カスタムフックとWordPressのデフォルトフックの両方)?多少のm笑が役立つと思いますが、私は何が欠けているのかわかりません。

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' ) );
  }
}

ありがとう!


を実行している場合phpunit、失敗または合格したテストを確認できますか?インストールしましたbin/install-wp-tests.shか?
スヴェン14年

問題の一部はRegisterCustomPostType::__construct()、テスト用にプラグインがロードされたときに呼び出されない可能性があることだと思います。バグ#29827の影響を受けている可能性もあります。WPの単体テストスイートのバージョンを更新してみてください。
JD 14年

@Sven:はい、テストは失敗しています。インストールしましたbin/install-wp-tests.sh(wp-cliを使用したため)@JD:RegisterCustomPostType :: __ construct 呼び出されます(die()ステートメントを追加し、phpunitがそこで停止します)
Ionut Staicu 14年

私はユニットテストの面(あまり得意ではありません)ではよくわかりませんが、文字通りの観点からはdid_action()、アクションが発生したかどうかを確認するために使用できます。
ラスト14年

@Rarst:提案に感謝しますが、まだ機能しません。何らかの理由で、タイミングが間違っていると思います(テストはinitフックの前に実行されます)。
Ionut Staicu 14年

回答:


72

単独でテストする

プラグインを開発する場合、WordPress環境読み込まにテストするのが最善の方法です。

WordPressなしで簡単にテストできるコードを書くと、コードはより良くなります

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

アイソレーター

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

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

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

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

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

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

そして、このテストが失敗すると仮定しましょう犯人は誰ですか?

  • フックがまったく追加されていないか、適切に追加されていませんか?
  • 投稿タイプを登録するメソッドがまったく呼び出されなかったか、引数が間違っていませんか?
  • 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つあります。

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

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

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

しかし...実際には?

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

「手動」メソッド

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

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

ブートストラップファイルからロードされたファイルがあるとします。

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();
    }

}

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

そして今、あなたのテストコード(私は上に投稿した書き換えられたクラスを参照):

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 を書き換える必要はありませんが、カスタムクラスで使用する抽象 WP関数は、それらを模擬し、簡単にテストできるようにします。

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

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

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

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

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

幸いなことに、そこには良いことを書く良い人がいます。最大のWPエージェンシーの1つである10upは、プラグインを正しい方法でテストしたい人のための非常に優れたライブラリを維持しています。それは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テストスイートは、WordPress コアをテストするために作成されたもので、コアに貢献したい場合は極めて重要ですが、プラグインに使用することで、単独でテストすることができません。

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

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

落とし穴と欠点

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

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

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

このような場合、WordPressテストスイートは非常に役立ちます。また、次のような機能を実行するためにWordPressのロードが必要になる場合があります。 dbDelta

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

幸いにもPHPUnitは、あなたのテストのすべての残り残してあなたはWordPressの環境(またはその一部)をロードするカスタムデータベースのテストのためのスイートを書くことができますので、あなたは、個別に実行することができ、「スイート」でテストを整理することができますワードプレスフリーを

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

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


5
神聖ながらくた、たくさんの有用な情報!ありがとうございました!どういうわけか、ユニットテストのすべてのポイントを見逃してしまいました(今までは、Code Dojo内でのみPHPテストを練習していました)。また、私は今日早くwp_mockについて知りましたが、何らかの理由でそれを無視することができます。私を怒らせたのは、どんなに小さなテストでも、実行に少なくとも2秒かかっていたことです(最初にWP envをロードし、次にテストを実行します)。目を開いてくれてありがとう!
Ionut Staicu 14年

4
ありがとう@IonutStaicu WordPressをロードしないとテストがずっと速くなることを忘れていました
。gmazzap

6
また、WP Core単体テストフレームワークは、統合テストを実行するためのすばらしいツールであり、WP自体との統合を保証する自動テストです(たとえば、偶発的な関数名の衝突がないなど)。
ジョンPブロッホ14年

1
@JohnPBloch +1が良い点です。名前空間を使用するだけで、すべてがグローバルであるWordPressでの関数名の衝突を回避するのに十分な場合でも:)しかし、確かに、統合/機能テストは重要です。今はBehat + Minkで遊んでいますが、まだ練習しています。
gmazzap

1
WordPressのUnitTestフォレストを「ヘリコプターに乗って」くれてありがとう-私はまだその壮大な写真を笑っています;-)
birgire 14年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.