LoginSignup
1

posted at

updated at

[Drupal]PHPUnitで基本のカーネルテスト

カーネルテストとは

DrupalのPHPUnitのテストには主にユニットテスト、カーネルテスト、ファンクショナルテストというものがあります。

スクリーンショット 2022-04-19 8.12.48.png

ユニットテストはDrupalのブートストラップ無し状態で行うテストです。一番高速ですがDrupalのサービスクラスなどを呼び出す際にいわゆる「モック」を作らなくてはいけないというデメリットがあります。またユニットテストでDBに接続するのは(頑張ればできるけど)非推奨とされています。

ファンクショナルテストはいわゆるブラウザテストで、新しくインストールされたDrupalに対して仮想ブラウザからリクエストを送る形でテストを行います。DrupalのコアとDBが使えたりユーザー目線のテストができるという反面実行速度が遅いというデメリットがあります。

カーネルテストはユニットテストとファンクショナルテストの中間に位置するもので、Drupalカーネルのブートストラップがされている状態なのでDrupalの各種サービスやDBは使えるけど、仮想ブラウザからのアクセスは行えないというものです。ユニットテストのようにサービスを使うたびにモックを書かなくてもよく、ファンクショナルテストより速いというメリットがあります。リクエストを行わないようなバックエンドの処理のテストに最適なのはもちろんですが、こちらのドキュメントを読む限りHTTPリクエストも作れるので、どうしてもブラウザからアクセスしないとダメっていう場合(UIのテスト等?)以外のファンクショナルテストはカーネルテストに落とし込むことができるのではないかなと思います。

ここらへんの話はこちらの記事が詳しく参考になりました :pray:

基本のカーネルテスト

今回はこんなサービスとテストを作成してみます。

  • 「[現在のユーザーのユーザー名]さん、Hello World!」という文字列を取得するだけのパブリックメソッドを持つサービスを作成する
  • 上記のメソッドがちゃんと「[現在のユーザーのユーザー名]さん、Hello World!」という文字列を返すかをテストする

(カーネルテストの特徴として「DBや構成にアクセスできる」というものがあるので、現在のユーザーのユーザー名にアクセスするようにしてみました)

テスト環境の用意

DrupalでPHPUnitのテストをする場合はまず以下のようにphpunit.xmlを編集する必要があります。

  • app/core/phpunit.xml.distをコピーしてapp/core/phpunit.xmlにリネーム
  • phpunit.xmlを以下のように編集
    • <env name="SIMPLETEST_BASE_URL" value=""/>valueにサイトのベースURLを入力(例: http://localhost:8080

    • <env name="SIMPLETEST_DB" value=""/>valueにデータベースURLを入力(例: mysql://username@localhost/my_database

  • この時点でcoreディレクトリに移動し../../vendor/bin/phpunit [任意のディレクトリ]を入力し、そのでディレクトリ下のテストが実行されればOK(特にコアやコントリビュートモジュールの/test/src/Unit(ユニットテストのディレクトリ)が早く実行できるのでオススメ)
../../vendor/bin/phpunit modules/datetime/tests/src/Unit

PHPUnit 7.5.20 by Sebastian Bergmann and contributors.

Testing Drupal\Tests\datetime\Unit\Plugin\migrate\field\DateFieldTest
......                                                              6 / 6 (100%)

Time: 191 ms, Memory: 6.00 MB

サービスの作成

以下のようにサービスファイルとservices.ymlを作成してカスタムモジュールに配置します。

my_module/src/Services/MyModuleService.php

<?php

namespace Drupal\my_module\Services;

use Drupal\Core\Session\AccountInterface;

/**
 * MyModuleのサービス.
 */
class MyModuleService {

  /**
   * コンストラクタ.
   *
   * @param \Drupal\Core\Session\AccountInterface $current_user
   *   現在のユーザー.
   */
  public function __construct(AccountInterface $current_user) {
    $this->currentUser = $current_user;
  }

  /**
   * 「現在のユーザーのユーザー名]さん、Hello World!」という文字列を返却する.
   *
   * @return string
   *   「現在のユーザーのユーザー名]さん、Hello World!」という文字列.
   */
  public function getHelloWorld() {
    $name = $this->currentUser->getAccountName();
    return $name . 'さん、Hello World!';
  }

}


my_module/my_module.services.yml

サービス名はmy_module_serviceとしました。サービスクラスのコンストラクタで使えるようにcurrent_userサービスを注入しています。

services:
  my_module_service:
    class: Drupal\my_module\Services\MyModuleService
    arguments: ['@current_user']

この時点でdrush phpでサービスが使えるか軽く確認します。you have requested a non-existent service等のエラー等が出る場合はymlやサービスクラスの内容が合ってるかもう一度確認してみてください。

>>> \Drupal::service('my_module_service')->getHelloWorld();
=> "さん、Hello World!"

drush phpのときは匿名ユーザーなので名前は空)

テストの作成

上記のサービスに対するテストを作成します。

my_module/tests/src/Kernel/MyModuleTest.php

<?php

namespace Drupal\my_module\tests\Kernel;

use Drupal\KernelTests\KernelTestBase;

/**
 * MyModuleサービスのテスト.
 */
class MyModuleTest extends KernelTestBase {

  use \Drupal\Tests\user\Traits\UserCreationTrait;

  /**
   * {@inheritdoc}
   */
  protected static $modules = [
    'user',
    'my_module',
  ];

  /**
   * {@inheritdoc}
   */
  protected function setUp() {
    parent::setUp();

    $this->currentUser = $this->setUpCurrentUser();

    $this->myService = \Drupal::service('my_module_service');
  }

  /**
   * HelloWorldを取得できるかをテストする.
   */
  public function testHelloWorld() {
    $hello_world = $this->myService->getHelloWorld();
    $name = $this->currentUser->getAccountName();
    $this->assertEqual($hello_world, $name . 'さん、Hello World!');
  }

}

ポイントは以下です。

  • ファイル名はXXTest.phpにする必要があります。(ディレクトリは適当でもいいっぽい?)
  • \Drupal::currentUser()で現在のユーザーを取得することもできますが、匿名(ユーザー名無し)になってしまうので、こちらのドキュメントの方法で認証済みユーザー(ユーザー名有り)を現在のユーザーとして取得しました。
  • $modulesuserを入れないとCall to undefined function Drupal\Tests\user\Traits\user_password()というエラーになるので入れました。
  • テストケースはtestXXという名前のパブリックメソッドにする必要があります。今回はtestHelloWorld()としました。
  • assertEqualなどカーネルテストで使用できるメソッドはこちらのドキュメントをご覧ください。

テストの実行

テストコードが出来たら、さきほどのように../../vendor/bin/phpunit ../modules/custom/my_module を実行し、以下のように表示されれば成功です。

Testing ../modules/custom/my_module
.                                                                   1 / 1 (100%)

Time: 709 ms, Memory: 6.00 MB

以下のようにテスト中にアウトプットを行いたい場合は、

  public function testHelloWorld() {
    $hello_world = $this->myService->getHelloWorld();
    $name = $this->currentUser->getAccountName();
    var_dump($name);
    $this->assertEqual($hello_world, $name . 'さん、Hello World!');
  }

phpunit.xmlbeStrictAboutOutputDuringTestsfalseにし、コマンドに--debugオプションを付けると以下のようにアウトプットを取得できます。(beStrictAboutOutputDuringTestsがtrueのままでも出力できますが、エラーメッセージが表示され見づらくなります)

Testing ../modules/custom/my_module
Test 'Drupal\my_module\test\Kernel\MyModuleTest::testHelloWorld' started
Test 'Drupal\my_module\test\Kernel\MyModuleTest::testHelloWorld' ended
rbfw3FyF <- $nameの値

Time: 676 ms, Memory: 6.00 MB

というわけで基本のカーネルテストの作り方でした。

参考

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
1