0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【PHPフレームワークFlow】Functionalテストにおけるテーブル作成単位の仕様確認

Last updated at Posted at 2025-10-04

はじめに

以前、FlowのFunctionalテストでDB接続を伴うAPIの試験を行った際に、マイグレーションがどの単位で実行されているのかを確認していませんでした。
今回はその点を詳しく調べてみようと思います。

Functionalテストとは

Functionalテストとは、Flowが提供する「アプリケーションの機能単位」を対象としたテスト機能です。
実際のリクエストやレスポンス、データベースを通して動作を確認できます。詳しくはこちらの記事をご参照ください。

DBアクセスのあるFunctionalテスト

実際のリクエストやレスポンス、データベースを通して動作を確認できます。
具体的な使い方や設定方法については、以下の記事でまとめたためご参照ください。

テスト前にあらかじめテスト用のデータベースを作成しておく必要がありますが、テーブルまで用意する必要はありません。Functionalテストの前に自動でテーブル作成がされる仕様になっています。

先の記事で紹介した実行結果が以下です。
一つのテストケースしか実行していないにも関わらず、実行に4秒もかかっています。
これは、実行前にテーブルを作成し、実行後にテーブルの削除が行われているためかと思われます(もちろんこれだけではないと思いますが)。

$ .\bin\phpunit -c .\Build\BuildEssentials\PhpUnit\FunctionalTests.xml .\Packages\Application\Neos.Welcome\Tests\Functional\Controller\FunctionalTestControllerTest.php --debug
PHPUnit 9.6.22 by Sebastian Bergmann and contributors.

Test 'Neos\Welcome\Tests\Functional\Controller\FunctionalTestControllerTest::functionaltest' started
Test 'Neos\Welcome\Tests\Functional\Controller\FunctionalTestControllerTest::functionaltest' ended


Time: 00:04.864, Memory: 92.00 MB

OK (1 test, 3 assertions)

では、複数のテストケースがある場合、テーブル作成はどの単位で行われるのでしょうか。

  • 最初の一回のみテーブル作成が行われる
  • 各テストケースごとにテーブル作成が行われる

また、テーブル作成が最初の一回のみの場合、トランザクションはどの単位で貼られるのでしょうか

  • すべてのテストケースで同一トランザクション?
  • 各テストケースごとにトランザクションをはりなおし、実行後にロールバック(or コミット)?

Functionalテストのテーブル作成単位を調べてみた

というわけでテストケースを作成し、検証していきましょう。

テストケース作成

以下3つのテストケースを作成しました。

  • データ取得APIのテスト(取得するデータはテストケースの中で作成)
  • データ登録APIのテスト
  • データ件数取得APIのテスト

最後のデータ件数取得APIで、データの件数が何件になっているのかを見れば、トランザクションがどの単位で貼られているのかのヒントがつかめるかもしれません。

テストケース
<?php

namespace Neos\Welcome\Tests\Functional\Controller;

use Neos\Flow\Tests\FunctionalTestCase;
use Neos\Welcome\Domain\Model\Functional;
use Neos\Welcome\Domain\Repository\FunctionalRepository;

class FunctionalTestControllerTest extends FunctionalTestCase
{

    /**
     * @var bool
     */
    protected $testableHttpEnabled = true;

    /**
     * @var boolean
     */
    protected static $testablePersistenceEnabled = true;

    /**
     * @var \Neos\Welcome\Domain\Repository\FunctionalRepository
     */
    protected $functionalRepository;

    /**
     * Additional setup
     */
    protected function setUp(): void
    {
        parent::setUp();
        $this->functionalRepository = $this->objectManager->get(FunctionalRepository::class);
    }

    /**
     * データ取得APIのテスト
     * @test
     */
    public function functionaltest()
    {
        // Arrange
        $testData = new Functional("testId", "testName");
        $this->functionalRepository->add($testData);
        $this->persistenceManager->persistAll();

        // Act
        $response = $this->browser->request('http://localhost:8081/Neos.Welcome/FunctionalTest/index');
        $responseArray = json_decode($response->getBody()->getContents(), true);

        // Assert
        self::assertSame('testId', $responseArray['id']);
        self::assertSame('testName', $responseArray['functionName']);
        self::assertSame(200, $response->getStatusCode());
    }

    /**
     * データ登録APIのテスト
     * @test
     */
    public function functionaltest2()
    {
        // Arrange
        $request = [];
        $request['key'] = 'testId';
        $request['name'] = 'testName';

        // Act
        $response = $this->browser->request('http://localhost:8081/Neos.Welcome/FunctionalTest/add', method: 'POST', arguments: $request);
        $responseArray = json_decode($response->getBody()->getContents(), true);

        // Assert
        self::assertSame(200, $response->getStatusCode());
    }

    /**
     * データ件数取得APIのテスト
     * @test
     */
    public function functionaltest3()
    {
        // Arrange
        $request = [];
        $request['key'] = 'testId';
        $request['name'] = 'testName';

        // Act
        $response = $this->browser->request('http://localhost:8081/Neos.Welcome/FunctionalTest/count');
        $responseArray = json_decode($response->getBody()->getContents(), true);

        // Assert
        self::assertSame(200, $response->getStatusCode());
        self::assertSame(0, $responseArray['count']);
    }
    
}

また、テスト実行中にmysqlサーバで以下のコマンドを何度か実行し、テーブルが存在しているのかを常に確認することにしましょう。
最初はテーブルが存在していないため、Errorになります。

mysql> select * from flow_functional_testing.functional;
ERROR 1146 (42S02): Table 'flow_functional_testing.functional' doesn't exist

実行

テストを実行してみました。

> .\bin\phpunit -c .\Build\BuildEssentials\PhpUnit\FunctionalTests.xml .\Packages\Application\Neos.Welcome\Tests\Functional\Controller\FunctionalTestControllerTest.php --debug
PHPUnit 9.6.22 by Sebastian Bergmann and contributors.

Test 'Neos\Welcome\Tests\Functional\Controller\FunctionalTestControllerTest::functionaltest' started
Test 'Neos\Welcome\Tests\Functional\Controller\FunctionalTestControllerTest::functionaltest' ended
Test 'Neos\Welcome\Tests\Functional\Controller\FunctionalTestControllerTest::functionaltest2' started
Test 'Neos\Welcome\Tests\Functional\Controller\FunctionalTestControllerTest::functionaltest2' ended
Test 'Neos\Welcome\Tests\Functional\Controller\FunctionalTestControllerTest::functionaltest3' started
Test 'Neos\Welcome\Tests\Functional\Controller\FunctionalTestControllerTest::functionaltest3' ended


Time: 00:14.700, Memory: 94.00 MB

OK (3 tests, 6 assertions)

データ件数取得 API の結果が 0 件だったことから、全てのテストケースが同一トランザクションで実行されているわけではないと分かりました。
また、テストケース3つで実行に14秒もかかっていることから、各テストケースごとにテーブル作成と削除をしている可能性は高そうです。

以下がmysqlでのselect文の実行結果の抜粋です。

mysql> select * from flow_functional_testing.functional;
ERROR 1146 (42S02): Table 'flow_functional_testing.functional' doesn't exist

mysql> select * from flow_functional_testing.functional;
Empty set (0.00 sec)

mysql> select * from flow_functional_testing.functional;
+--------------------------------------+--------+----------------+
| persistence_object_identifier        | id     | functionalname |
+--------------------------------------+--------+----------------+
| cb184e1a-7ebf-4e91-9865-581480018c3c | testId | testName       |
+--------------------------------------+--------+----------------+
1 row in set (0.00 sec)

mysql> select * from flow_functional_testing.functional;
ERROR 1146 (42S02): Table 'flow_functional_testing.functional' doesn't exist

mysql> select * from flow_functional_testing.functional;
Empty set (0.00 sec)

mysql> select * from flow_functional_testing.functional;
+--------------------------------------+--------+----------------+
| persistence_object_identifier        | id     | functionalname |
+--------------------------------------+--------+----------------+
| 32a9eea8-3f9b-4f74-9bde-ebf63b1d8aaf | testId | testName       |
+--------------------------------------+--------+----------------+
1 row in set (0.00 sec)

mysql> select * from flow_functional_testing.functional;
ERROR 1146 (42S02): Table 'flow_functional_testing.functional' doesn't exist

mysql> select * from flow_functional_testing.functional;
Empty set (0.00 sec)

mysql> select * from flow_functional_testing.functional;
ERROR 1146 (42S02): Table 'flow_functional_testing.functional' doesn't exist

cb184e1a-7ebf-4e91-9865-581480018c3cというデータが取得できた次の行では、取得結果がERRORとなっています。このことから、最初のテストケースが実行された後、テーブルが削除されていることが分かります。

まとめ

Functionalテストでは、各テストケースごとにテーブルを作成、削除している
ということが分かりました。

おわりに

テーブルを作成しなおしているため、テスト結果がフレーキーになることがないというのは大きなメリットですが、一回の実行に4秒もかかるなど課題は残りますね。並列実行や実行対象を絞るなど、運用で対策が必要かと思います。

ここまでご覧いただきありがとうございました!

0
0
0

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
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?