😇

【PHPUnit9->11】#[DataProviderExternal()]を使うんだ!

2024/07/27に公開

結論

PHPUnit11以降では#[DataProviderExternal()] を使いましょう

🚨 #[DataProvider()]ではテストクラス外のデータプロバイダークラスのメソッドを参照することができません。
(なおデータプロバイダークラスのメソッドはstaticである必要があります)

私はここでハマって1日溶かしました・・・🫠

対象読者

  • PHPUnitのバージョンアップ対応を迫られている人へ
  • PHPUnit9.x->11.xに上げたら@DataProviderアノテーションが非推奨になってて、どうしたものかと思ったそこのあなたへ
  • アトリビュート#[Dataprovider(Foo::class, 'FooTestData')]を使って、データプロバイダークラスのメソッドもstaticにしたのにテストエラーになって困ってるそこのあなたへ
    こんな感じになってしまう↓

Tests\Unit\Services\Foo\FooExampleServiceTest::testFooExample
The data provider specified for Tests\Unit\Services\Foo\FooExampleServiceTest::testFooExample is invalid
Method
Tests\Unit\Services\Foo\FooExampleServiceTest::testFooExample::Tests\Unit\Services\DataProviders\Foo\FooExampleServiceTestDataProvider() does not exist

なぜか テストクラス内にデータプロバイダーメソッドがないぜ!おらぁん!? と怒られてしまいますね。ごめんて🙏

くわしく

まずは公式ドキュメントを読みましょう
https://docs.phpunit.de/en/11.2/

また、PHPのアトリビュート記法は8.0で登場しておりますので、本記事で取り扱うサンプルコードはPHP8.0以上であることが前提となります。

📝 アトリビュートの概要
https://www.php.net/manual/ja/language.attributes.overview.php

(注: なお8.0のEOLは2023年11月の模様)
https://www.php.net/supported-versions.php

PHPUnit9.xではこうだった

NanikasiraTest.php
<?php declare(strict_types=1);

use PHPUnit\Framework\TestCase;
use path\to\NanikasiraTestDataprovider; // データプロバイダー

class NanikasiraTest extends TestCase
{
    /**
    * なんらかのテスト
    *
    * @dataprovider Foo::testSomethingDataSet()
    * @params int    $score
    * @params string $message
    */
    public function test_あれをこれしたときそれになること(int $score, string $message):void {
        // なんらかの検証を行う
    }
}
NanikasiraTestDataprovider.php
<?php declare(strict_types=1);

class NanikasiraTestDataprovider
{
    public function testSomethingDataSet(): array
    {
        return [
            [1000000,  '壱百満天💯'],
            [-1000000, 'マイナス壱百満天😢'],
        ];
    }
}

PHPUnit11.xではこう

NanikasiraTest.php
<?php declare(strict_types=1);

use PHPUnit\Framework\Attributes\DataProviderExternal; // これ
use PHPUnit\Framework\TestCase;
use path\to\NanikasiraTestDataprovider; // データプロバイダー

class NanikasiraTest extends TestCase
{
    /**
    * なんらかのテスト
    *
    * @params int    $score
    * @params string $message
    */
    #[DataProviderExternal(NanikasiraTestDataprovider::class), 'testSomethingDataSet']
    public function test_あれをこれしたときそれになること(int $score, string $message):void {
        // なんらかの検証を行う
    }
}

テストクラスでは、アトリビュートを使う形に変更します。

NanikasiraTestDataprovider.php
<?php declare(strict_types=1);

class NanikasiraTestDataprovider
{
    public static function testSomethingDataSet(): array
    {
        return [
            [1000000,  '壱百満天💯'],
            [-1000000, 'マイナス壱百満天😢'],
        ];
    }
}

メソッドはstaticにする必要があります。

https://docs.phpunit.de/en/11.2/attributes.html#dataproviderexternal

The DataProviderExternal(string $className, string $methodName) attribute can be used on a test method to specify a static method that is declared in another class as a data provider.

DataProviderExternal(string $className, string $methodName) 属性をテストメソッドで使用すると、 別のクラスで宣言されている静的メソッドをデータプロバイダとして指定することができます。

ドキュメントにこのように明記されています。
・・・明記されているのですが、私の調査能力が足りなかったのか気づくまでにえらく時間がかかってしまいました。思い込みって怖い・・・

まとめ

  • PHPUnit11では@DataProviderは非推奨となりました(実行時Deprecate扱いに)
  • PHPUnit11では#[DataProvider('Foo')]と書くと、テストクラス外のデータプロバイダークラスのメソッドを参照しません(テストクラス内に静的データプロバイダーメソッドがあれば参照します)

ちなみに

PHPUnit9リリース時点(2020/02/07)ではPHP8.0自体がリリースされていなかった(2020/11/26)ため、PHPのアトリビュートに関する機能は提供されていませんでした。

そのため、DataProviderExternalのアトリビュートが登場したのはPHPUnit10からのようです。
https://docs.phpunit.de/en/10.5/attributes.html#dataproviderexternal

ひとりごと

@DataProviderが非推奨となったという件についての記事・スライドはいくつかあったのですが、
DataProvider属性の仕様自体が変更されていることについて言及されている記事がなかなか見つからず、それなら自分で書いちゃえ、と思って久々に技術ブログ的なものを生成してみました。

あと、#[DataProvider('Foo')]じゃなくて#[DataProviderInternal('Foo')]に改名してDataProvider自体を廃止してほしかった感。
それかDataProviderの仕様はそのままにしてDataProviderInternalを新設して欲しかった・・・
きっとPHPUnitのissueの流れとか追えばこうなった経緯がわかるんだろうなあと思うので後でゆっくり眺めてみようと思います。

Discussion