Chapter 12

サービスクラスを作成したらユニットテストを書く

たつきち
たつきち
2022.07.30に更新

この章に対応するコミット

サービスクラスを作成したらユニットテストを書く

機能テストだけでなくユニットテストを書く例も紹介します。

怖い人たちからは怒られそうですが、僕は個人的にはカバレッジ100%とかには全然こだわっていなくて、感覚として不安なところだけテストを書くというスタンスです🙏

コードを追加・修正したときに 画面を操作して動作確認したくなったら、その操作の代わりに自動テストを書く という感じでしょうか。

なお、自動テスト・ユニットテストについては以下の過去記事でとても詳しく解説していますので、ぜひご参照ください✋

【ユニットテスト入門】自動テストの意義とPHPでの具体的な使い方【基礎編】

例えば、

  • ユーザーの表示名として 金本 貴志 のように全角スペース入りの値が入力されたときに 金本 貴志 と半角スペースに置き換えて保存するようにしたい
  • 会社名のような項目に 株式会社HOGE のように全角英数字入りの値が入力されたときに 株式会社HOGE と英数字だけを半角に置き換えて保存するようにしたい

といった要件を考えます。

実際にこういう要件は普通によくあって、人名や会社名の項目で全角スペースや全角英数字を半角に強制変換するというサービスは僕の場合ほとんど毎回作っています。

まずはこのサービスを実装して、UserListener から利用するようにします。

サービスを実装する

こんな内容にしてみます。

// src/Service/Namer.php

namespace App\Service;

class Namer
{
    public function beautify(?string $name): ?string
    {
        return $name === null ? null : mb_convert_kana(trim(preg_replace('/( | )+/', ' ', $name)), 'a');
    }
}

mb_convert_kana() を使ったので、忘れずに composer require ext-mbstring:* しておきましょう。

  "require": {
      "php": "^7.4",
      "ext-ctype": "*",
      "ext-iconv": "*",
+     "ext-mbstring": "*",
      "cakephp/chronos": "^2.0",
      :

そしてこのサービスを UserListener::preFlush() から呼び出して、ユーザーの保存前に displayName の内容に適用するようにします。

  class UserListener
  {
      private UserPasswordEncoderInterface $encoder;
+     private Namer $namer;
  
-     public function __construct(UserPasswordEncoderInterface $encoder)
+     public function __construct(UserPasswordEncoderInterface $encoder, Namer $namer)
      {
          $this->encoder = $encoder;
+         $this->namer = $namer;
      }
  
      public function postLoad(User $user, LifecycleEventArgs $event)
      {
          $user->displayName = $user->displayName ?? $user->email;
      }
  
      public function preFlush(User $user, PreFlushEventArgs $event)
      {
          if ($user->plainPassword) {
              $user->password = $this->encoder->encodePassword($user, $user->plainPassword);
          }
+ 
+         $user->displayName = $this->namer->beautify($user->displayName);
      }
  }

動作確認

これで、例えば以下のように    Takashi    Kanemoto    みたいな不細工な入力をしても、

Takashi Kanemoto ときれいな形で保存されるようになりました👍

サービスのユニットテストを書く

さて、サービスを作ったらユニットテストです。

冒頭で「不安なところしかテストは書かない」と言いましたが、今回作ったサービスなんかは処理は単純ですが本当に期待どおりの出力結果になるかどうかは動かしてみないと結構不安な内容ですよね。(この感覚伝わりますかね…)

というわけでユニットテストを書きます。

// tests/Service/NamerTest.php

namespace App\Service;

use PHPUnit\Framework\TestCase;

class NamerTest extends TestCase
{
    private $SUT;

    protected function setUp(): void
    {
        $this->SUT = new Namer();
    }

    public function testBeautify()
    {
        $this->assertNull($this->SUT->beautify(null));
        $this->assertEquals('株式会社 HOGE FUGA', $this->SUT->beautify('  株式会社  HOGE  FUGA  '));
    }
}

とりあえず今回であればこれだけで十分でしょう。

今後もしサービスの機能を追加変更したり、想定できていなかったケースが見つかったりしたらテストケースを追加したり修正したりしていく感じです✋

+ /** @group tmp */
  public function testBeautify()
  {
      $this->assertNull($this->SUT->beautify(null));
      $this->assertEquals('株式会社 HOGE FUGA', $this->SUT->beautify('  株式会社  HOGE  FUGA  '));
  }
$ bin/console --group tmp
PHPUnit 9.4.3 by Sebastian Bergmann and contributors.

Testing
.                                                                   1 / 1 (100%)

Time: 00:00.049, Memory: 10.00 MB

OK (1 test, 2 assertions)

こんな感じで実行してみて、ちゃんと期待どおりに動作していることが確認できました。

今後もこんなふうに、何かサービスクラスを作ったら都度ユニットテストを書く、というのが基本的な流れになります👍