この記事について

PHP/Laravelを利用してWebアプリケーションを実装していました。インフラにはECS Fargateを利用して、CodeシリーズでCI/CDパイプラインを実装しています。

CI/CDパイプラインの中にて、LaravelのFeatureテストとUnitテストを実行できるようにパイプラインを改修したところ、テストが失敗してパイプラインが中断されてしまいます。

確認したところ、テストは全てPassしており、Failedとなっている箇所はないように見受けられます。ところがよく見てみると、php artisan testコマンドの終了ステータスが1になっており、これが原因でパイプラインが中断されているようです。

なぜテストは全てPassしているのに、終了ステータスが0にならないのか?
この原因と解決策についてこちらの記事に残します。

PHP/Laravelのバージョン

  • Laravel : 10
  • PHP : 8.2

インフラ構成

全体像

パイプライン構成

よくあるECSのCI/CDパイプライン構成です。

  • [ソースステージ] CodeCommit
  • [ビルドステージ] CodeBuild
  • [デプロイステージ] CodeDeploy

というステージ構成です。
ビルドステージの中で、コンテナのビルド/テストの実行/ECRへのPushを行っています。

ビルドステージ

buildspec.ymlの一部を抜粋します。

  pre_build:
    commands:
      - AWS_ACCOUNT_ID=$(echo ${CODEBUILD_BUILD_ARN} | cut -f 5 -d :)
      - echo Logging in to Amazon ECR...
      - aws --version
      - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com
      - REPOSITORY_URI=${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/clpplus-${ENV}-client/app
      - DOCKERFILE_PATH=${DOCKERFILE_PATH}
      - COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
      - IMAGE_TAG=${COMMIT_HASH}
  build:
    commands:
      # コンテナのイメージをビルド
      - cd ./sources
      - composer install
      - cp .env.example .env
      - docker build -f $DOCKERFILE_PATH -t app:latest
      # テスト環境を立ち上げ
      - docker-compose up -d
      # テストの実行
      - docker compose exec test-app php artisan migrate
      - docker compose exec test-app php artisan test
      # テスト環境を削除
      - docker-compose down
  post_build:
    commands:
      - docker tag app:latest $REPOSITORY_URI:$IMAGE_TAG
      - docker push $REPOSITORY_URI:$IMAGE_TAG
      - printf '{"ImageURI":"%s"}' $REPOSITORY_URI:$IMAGE_TAG > imageDetail.json

ここで注目して欲しいのは、buildフェーズにてdocker composeを利用してテスト環境を立ち上げているということです。CodeBuildのビルド自体もコンテナ上で行われますが、さらにその上でテスト用のコンテナを立ち上げる形になります。

テスト環境が立ち上がったら、マイグレーションを行い、テストを実行しています。

起きていたこと

パイプラインを実行させたところ、ビルドステージが失敗しました。

確認したところ、docker compose exec test-app php artisan testが原因で失敗している模様です。
ログを確認したところ

[Container] 2024/01/12 12:46:30.199307 Running command docker compose exec test-app php artisan test

... 省略 ...

Tests: 770 passed (4414 assertions)
Duration: 202.34s

全てのテストケースがPassしています。

ちなみに、もしFailedになれば以下のようになります。

[Container] 2024/01/12 12:46:30.199307 Running command docker compose exec test-app php artisan test

... 省略 ...

Tests: 3 failed, 767 passed (4382 assertions)
Duration: 291.08s

なぜ全てPassとなっているのに、ビルドステージは失敗したのでしょうか?

デバッグを行っていくうちにdocker compose exec test-app php artisan testの終了ステータスが1になっていることがわかりました。

テストが成功した場合、終了ステータスは0になるはずです。全てPassになってはいますが、終了ステータスが1になっているということはどこかに問題があるということになります。

原因/解決策

原因としては、テストケースが記載されてないテストファイルがあったためです。

テスト対象に以下のようなファイルが含まれていました。

<?php

namespace Tests\Unit\Services;

use App\Models\Hoge;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

/**
 * App\Services\HogeServiceTestのテスト。
 */
class HogeServiceTest extends TestCase
{
    use RefreshDatabase;

    /**
     * @var Hoge $hoge
     */
    protected $company;

    /**
     * {@inheritdoc}
     */
    public function setup(): void
    {
        parent::setup();

        $this->hoge = Hoge::factory()->create();
    }
}

本来であれば、このファイルにはテストケースが記載されているはずでした。しかしまだテストの実装がされておらず、テストファイルの叩き台だけが作成されている状況でした。

この場合、テストケースが書いていないので、何も実行されないためFailedが出るようなことがありません。

しかし、テストケースが定義されていないテストファイルがあると、正常にテストが完了したと判断されず、終了ステータスが1になるようです。

試しに以下のようなテストケースを記載したところ、終了ステータスは0になり、パイプラインの実行も成功しました。

   /**
     * @test
     */
    public function sample()
    {
        $this->assertTrue(true);
    }

参考

https://github.com/sebastianbergmann/phpunit/issues/4299

最後に

実行結果に何もエラー文などが表示されておらず、終了ステータスだけが手掛かりだったので、原因に辿り着くのに時間がかかりました。。

もしこの記事が同じように困っている人の助けになれば嬉しいです。