LIFULL Creators Blog

「株式会社LIFULL(ライフル)」の社員によるブログです。

E2Eテストをクラウド移行したお話

LIFULLで売却査定サイトの開発をしています、北島です。
このたびTestCafeというE2Eテストを、awsのリソースを使ってクラウド移行しましたので、簡単に振り返りたいと思います。

前提

売却査定のサービスは本番を含めて4つの環境が用意されています。

  • prod環境(本番)
  • pool環境(開発環境)
  • dev環境(開発環境)
  • unit環境(開発者各々の環境)

これらのうちunit環境以外の3環境に関して、デプロイをトリガーにE2Eテストを行うような仕組みを実現しました。

構成図

CodeBuild上でTestCafeを実行することで実現を試みました。
テストに関する構成図を下に示します。

構成図
構成図

CodeDeploy(既存)のデプロイ成功時にSNSメッセージを発行し、LambdaFunctionからCodeBuildを実行します。
CodeBuildでのテスト実行終了時に、テスト結果を含むSNSメッセージを発行し、notificationで実行結果を通知します。
構成図内の"env"は、実際にはdev/pool/prodと環境ごとに別々のリソースに分かれています。

awsリソースのメインとなるのはtestcafe_kickerのリポジトリで、テストに関するawsリソース全般に関して扱っています。
実際に使用されるテストコードに関してはcodebuild-testcafeのリポジトリで管理しています。

testcafe_kickerリポジトリ

このリポジトリの役割は、CodeBuildを実行するlambdaを管理することです。
今回使用したserverlessというフレームワークでは、CloudFormationテンプレートを使用することで、awsリソースをデプロイすることができます。
lambdaを管理するリポジトリながら、CodeBuildプロジェクトやsnsトピック、及びそれらの間の通知ルールの一括管理が可能となっています。

CodeBuild projectについて

メインとなるCodeBuild projectの実装について書いていきます。

serverless.yml

templates: # anchor用template定義用
  dev_topicName: &testcafe_dev_topic_name testcafe_kicker_dev
  pool_topicName: &testcafe_pool_topic_name testcafe_kicker_pool
  prod_topicName: &testcafe_prod_topic_name testcafe_kicker_prod
  project_name: &testcafe_project_name CodeBuild-TestCafe
  build_project_properties: &testcafe_build_project_properties
    Artifacts:
      Type: NO_ARTIFACTS
    BadgeEnabled: true
    BuildBatchConfig:
      ServiceRole: !GetAtt TestcafeBuildRole.Arn
    ServiceRole: !GetAtt TestcafeBuildRole.Arn
    VpcConfig:
      VpcId: vpc-******
      Subnets:
        - subnet-******
      SecurityGroupIds:
        - sg-******
    Source:
      Auth:
        Type: OAUTH
      Location: https://github.com/{#testcafeリポジトリのurl}
      GitCloneDepth: 1
      Type: GITHUB
    SourceVersion: refs/heads/master

~中略~

  Resources:
    TestcafeBuildProjectDev:
      DependsOn:
        - TestcafeBuildPolicies
      Type: AWS::CodeBuild::Project
      Properties:
        <<: *testcafe_build_project_properties
        Name: !Join
            - '-'
            - - *testcafe_project_name
              - Dev
        Environment:
          ComputeType: BUILD_GENERAL1_SMALL
          Image: aws/codebuild/amazonlinux2-x86_64-standard:3.0
          Type: LINUX_CONTAINER
          PrivilegedMode: true
          EnvironmentVariables:
            - Name: TESTCAFE_DOMAIN
              Type: PLAINTEXT
              Value: https://www-test-~~(テスト対象のドメイン)
            - Name: ECR_REGION
              Type: PLAINTEXT
              Value: !Ref AWS::Region
            - Name: ECR_URI_LATEST
              Type: PLAINTEXT
              Value: !Join
                  - ''
                  - - !Ref AWS::AccountId
                    - '.dkr.ecr.'
                    - !Ref AWS::Region
                    - '.amazonaws.com/'
                    - !Ref TestcafeEcrRepository
                    - ':latest'

ロールやセキュリティグループなどをしっかりと定義する必要があります。
またgithub上のテストコードをcloneして実行するので、リポジトリのurlも設定します。
privateリポジトリの場合、認証情報もあらかじめawsコンソール上で登録しておき、cloneに成功するようにしておく必要があります。
テスト実施時のコマンドは、このプロジェクト自体に定義するほか、cloneしたリポジトリ内で定義されたものを使うようにも設定できます。
実際のテスト実施時のコマンドはテストコード毎に異なってくるので、このリポジトリではなくTestCafeリポジトリで定義されたものを使うようにしています。
buildspecに書けないテスト対象のドメインなどは、プロジェクトの環境変数としてテンプレートに定義しておきます。

TestCafeリポジトリ

このリポジトリの役割は、TestCafeのテストコードを管理することです。
ローカル実行できるようなテストコードを作成しています。
これをDocker内でも実行できるように調整したうえで、CodeBuild上で行いたいコマンドをbuildspec.ymlに記述することで、CodeBuild プロジェクトでも実行できるようになります。
buildspec.ymlを見ていきます。

version: 0.2

batch:
  build-list:
    - identifier: pc_page
      env:
        variables:
          UA: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/604.1.38 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/604.1'
          testcase: "'chromium:headless' tests/page/"
    - identifier: pc_inquire
      env:
        variables:
          UA: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/604.1.38 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/604.1'
          testcase: "'chromium:headless' tests/pc/inquire"
          OVERWRITE_COOKIE: 'abtest_AB=a;'
    - identifier: sp_page
      env:
        variables:
          UA: 'Mozilla/5.0(iPad;CPUOS11_0likeMacOSX)AppleWebKit/604.1.34(KHTML,likeGecko)Version/11.0Mobile/15A5341fSafari/604.1'
          testcase: "'chromium:headless:emulation:device=iphoneX' tests/page/"
    - identifier: sp_inquire
      env:
        variables:
          UA: 'Mozilla/5.0(iPad;CPUOS11_0likeMacOSX)AppleWebKit/604.1.34(KHTML,likeGecko)Version/11.0Mobile/15A5341fSafari/604.1'
          testcase: "'chromium:headless:emulation:device=iphoneX' tests/sp/inquire/"
    - identifier: bot_page
      env:
        variables:
          UA: 'Googlebot/2.1 (+http://www.google.com/bot.html)'
          testcase: "'chromium:headless:emulation:userAgent=Googlebot/2.1(+http://www.google.com/bot.html)' tests/page/"

phases:
  install:
    commands:
      - yum update -y
      - yum install -y bind-utils
  pre_build:
    commands:
      - aws ecr get-login --no-include-email --region ${ECR_REGION}
      - docker pull ${ECR_URI_LATEST} || true # cache利用目的のpullなのでエラーでもビルドを続ける
      - docker build --cache-from ${ECR_URI_LATEST} --tag ${ECR_URI_LATEST} .
  build:
    commands:
      - export POOL_IP=`dig +short (pool環境のCDN) | tail -n1`
      - docker run --env TESTCAFE_DOMAIN --env UA --env OVERWRITE_COOKIE --add-host (poolのドメイン):${POOL_IP} ${ECR_URI_LATEST} /usr/bin/npx testcafe ${testcase} --disable-multiple-windows -q
  post_build:
    commands:
      - aws ecr get-login-password --region ${ECR_REGION} | docker login --username AWS --password-stdin ${ECR_URI_LATEST}
      - docker push ${ECR_URI_LATEST}

大きく二つ、batchとphaseから構成されています。

phase

phaseでは、ビルド内で実際に行いたいテストコマンドを書いていきます。
installやpre_buildなどの詳細なphaseの名前はあらかじめawsによって用意されているので、より細かく分けたい場合はどんなものが使えるのかを調べる必要があります。
ここに書くコマンドはどのビルドでも使用されるので、なるべく簡潔に汎用的に書くことを心掛けました。 docker imageはaws ecrを使用してキャッシュするようになっています。

どこかのphaseで一つのコマンドが正常終了しなかった時点でそのビルドは停止してしまいます。
複数テストコマンドを記述しても途中で終わってしまっては後のテストが実施されないので、テストを行う(正常終了しない可能性のある)コマンドは、一つのビルドにつき1つが望ましいと考えました。
一つのプロジェクトで複数のテストを行いたかったので、今回はバッチビルドによって実現しています。

batch

バッチビルドでは、一つのプロジェクトから複数のビルドを作成することが出来ます。
batchという項目ではそれぞれのビルドをどのように、どのような設定で作成するかを定義しています。
build-listは、リストの形で作成するということを意味しており、他にbuild-graphやbuild-matrixという方法が用意されています。
build-listは配列で定義しており、各項目が一つのビルドの設定になっています。なので今回の設定では5つのビルドが作成されるわけです。
identifierはユニークな文字列で設定し、各ビルドの名前になります。わかりやすい文字列が良いでしょう。
envの中ではビルドごとに変えたいパラメータを設定でき、variables内では環境変数を設定できます。
ここでは以下の3つを環境変数として定義しています。

  • テストを行うUA
  • テストコード(testcase)
  • 特に指定したいcookie(OVERWRITE_COOKIE)

これらを設定することで、色々な条件でテストを行うことが出来るようになりました。

検討事項

今回はbuild-listを使用しましたが、build-graphとbuild-matrixの使用も検討しました。
それぞれの性質について、簡単にご紹介します。

build-matrix

最初に検討したのはbuild-matrixで、これはmatrixとあるように、色々な変数に関して、全組み合わせでビルドを作成します。
例えば、今回は3つの環境変数を使っていますが、

  • UA2種
  • testcase3種
  • OVERWRITE_COOKIE2種

の様に設定した場合、build-matrixでは2*3*2の全組み合わせ、つまり全12個のビルドを作成します。
これはいろいろな環境でビルドを行いたい場合に非常に便利で、当初はこちらを使用していました。
デメリットとしては、例外パターンを作れないことです。全組み合わせに近い11のビルドだけ作成、が出来ない点がデメリットだと思います。
今回私が実現したかったテストは、UAによって行いたいテストケースが異なっており、特にbotではステータスチェックのみを行いたいテストでした。
matrixでは不要なビルドが出来てしまい、matrixの恩恵を受けられないと感じました

build-graph

build-graphは各ビルドの依存関係を作ることが出来、このビルドが終わったら次のこのビルドを作成、のような設定が作れます。
これも便利な機能で、より詳細にテストを計画することが出来ます。
デメリットは並列実行よりも実行時間が長くなるという点で、より早く行いたい場合はlistやmatrixの方が良いですね。
今回はテスト間に依存関係が無かったので、時間を優先してlistとしました。
しかし特に本番以外の環境に対してテストを行う場合、並列実行数によってはサーバー負荷が問題になってきます。
もしもビルドが増えてきて並列実行が難しくなった場合は、開発用環境のスペックを上げる選択肢だけでなく、build-graphに変更して負荷を減らす選択肢も考えたいと思います。

苦労したこと

ネットワーク関連の設定や検証に苦労しました。
CodeBuild プロジェクト自体は開発用の環境に構築したのですが、poolやprod環境は別のawsアカウントを使用しているため、vpcが異なります。
pool環境は通常売却査定のVPC外からアクセスするため、通常の方法では疎通ができませんでした。
最終的に今回は、TestCafeの項のyamlにもあるように、自VPCからでもアクセス可能であったCDNのIPを動的に取得し、それをdockerコマンドのadd-hostオプションとして渡して実行する方法で解決しました。

まとめ

今回は、デプロイ後にE2Eテストを行う工程をCodeBuildを使いクラウド移行しました。
ローカルで行っていたテストが自動化できたので、開発者の時短や、アプリケーションの品質担保に貢献できたと思います。
今後もローカルで行っているフローをクラウド移行していければ良いなと考えています。

ここまで読んでいただき、ありがとうございました。