🎃

[PHPUnit]attributesを使ってテストデータを効率的に管理する

2025/01/17に公開

はじめに

こんにちは、booost technologiesバックエンドエンジニアのma_meです。
チーム内でユニットテストの熱が高まる中、PHPUnitの知識が数年前で止まっていたので、がんがんアップデート中です。
PHPにattributesが導入されてから、PHPUnitも追従してattributesが使えるようになっていました。
従来アノテーションを使って記法していたときは、静的解析が効きづらい、補完が効かないといった課題がありましたが、PHPUnitのattributesではこれらの問題が解消されています。
今回は特に便利だと感じた、PHPUnitのattributesを使ってデータセットを用意する方法と、アノテーションとの比較点をまとめました。

参考
PHPUnit公式ドキュメント

TestWith Attributes

TestWith Attributesの特徴は以下の通りです。

  • メソッド定義不要
  • クラス定義不要
  • テストデータをテストメソッドのすぐ上に記述できる

感覚的にJestのeachのように書けるので、親しみやすいと感じました。

書式

#[TestWith([データセット])]
public function テストメソッド名(): void

準備

各Attributeをuseする

use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\TestWith;

サンプルコード

<?php
namespace Tests;

use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\TestWith;
use PHPUnit\Framework\TestCase;

class SampleTest extends TestCase
{
    #[Test]
    #[TestWith([0, 0, 0])]
    #[TestWith([0, 1, 1])]
    public function 数字加算(int $a, int $b, int $expected): void
    {
        $this->assertSame($expected, $a + $b);
    }

    #[Test]
    #[TestWith(['booost', 'technologies', 'booost technologies'])]
    public function 文字連結(string $a, string $b, string $expected): void
    {
        $this->assertSame($expected, $a . ' ' . $b);
    }
} 

@testWith アノテーションとの違い

annotationのケースでは下記のように書きました。

/**
 * @testWith ["test", 4]
 *           ["longer-string", 13]
 */

以前はこのような問題を抱えていました。

  • コメント扱いなので、補完が効きづらい
  • 静的解析が効きづらい
  • 文字列は必ずダブルクォートで記載する必要がある

これらの要素がTestWith Attributesで解消されています。

DataProviderExternal Attributes

DataProviderExternal Attributesの特徴は以下の通りです。

  • メソッド定義とクラス定義が必要
  • クラス定義とメソッド定義を個別に指定して使える
  • テストコードとデータセットを分離できる

データセットを使いまわしたい場合や、テストコードにデータセットを記述したくない場合にはとても便利です。

書式

#[DataProviderExternal(データセットのクラス名, '呼び出したいメソッド名')]
public function テストメソッド名(): void

準備

各Attributeとデータセットのクラスをuseする

use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\DataProviderExternal;
use データセットクラス

サンプルコード

データセットクラス

<?php

namespace Tests\DataProvider;

use PHPUnit\Framework\TestCase;

class SampleTestDataProvider extends TestCase
{
    public static function 数字加算DataSet(): array
    {
        return [
            [0, 0, 0],
            [0, 1, 1],
        ];
    }

    public static function 文字連結DataSet(): array
    {
        return [
            ['booost', 'tech', 'booost tech'],
            ['booost', 'technologies', 'booost technologies']
        ];
    }
}

テストコード

<?php
namespace Tests;

use Tests\DataProvider\SampleTestDataProvider;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\DataProviderExternal;

class SampleTest extends TestCase
{
    #[Test]
    #[DataProviderExternal(SampleTestDataProvider::class, '数字加算DataSet')]
    public function 数字加算2(int $a, int $b, int $expected): void
    {
        $this->assertSame($expected, $a + $b);
    }

    #[Test]
    #[DataProviderExternal(SampleTestDataProvider::class, '文字連結DataSet')]
    public function 文字連結2(string $a, string $b, string $expected): void
    {
        $this->assertSame($expected, $a . ' ' . $b);
    }
}

DataProvider アノテーションとの違い

DataProviderExternalアノテーションに該当するものは無く、DataProviderアノテーションがありました。
DataProviderアノテーションでは下記のように書けました。

/**
 * @dataProvider データプロパイダーメソッド名
 */

以前はこのような問題を抱えていました。

  • データプロバイダーメソッドを同じファイルに記載する必要があった
  • データセットの使いまわしが同クラス内でしかできなかった。

この点がDataProviderExternal Attributesでは解消されています。

アノテーションとattributesの比較まとめ

特徴 アノテーション attributes
静的解析の対応 効きづらい 効く
テストデータの記述場所 コメント内 PHP構文そのもの
データセットの場所 同じファイル内 外部クラスから呼び出し可能
記述の簡潔さ やや複雑 シンプルで読みやすい
サポートする構文 PHPDoc形式での記述 ネイティブなPHP構文

この記事が、効率的なテストコード作成の参考になれば幸いです

宣伝

booost technologiesではPHPエンジニアを積極採用中です。
もしご興味をお持ちいただけましたら、ぜひお気軽にお声がけください。
https://hrmos.co/pages/booosttechnologies/jobs?category=1790705628757647362

booost technologies

Discussion