GitHubとBuckyのラベルを使ってE2Eテストをシフトレフトする

# はじめに

CIでE2Eテストを回すときに、問題となるのは**実行時間**です。
弊社で用意しているE2Eテストはすべてのテストケースを実行するのに40〜50分かかります。
これを毎Push、毎Pull Requestごとに実行するのはちょっとつらいです。

開発ブランチにおいては、**修正した箇所のみ**E2Eテストを実行できたら嬉しいですよね。

LIFULLのSETGで使っているテストツール[Bucky](https://www.lifull.blog/entry/2019/05/21/170131)には実行対象となるテストケースをラベルで絞り込む機能があります。
今回はそのBukcyの**ラベル機能**とGitHubの**Pull Requestのラベル**を組み合わせることで、テストしたい部分にだけCI上でE2Eテストをかけられる仕組みを考えました。
テストを実行する範囲のイメージとしてはこんな感じです。

<img width="536" alt="スクリーンショット 2019-12-21 23.11.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/156024/88514b2b-0b39-6f26-e68e-bcea3b6e1a35.png">


※リグレッションテストの効果が薄まるので、統合環境ではすべてのテストを実行するようにしましょう。

# テスト対象

テスト対象はBuckyのテスト結果表示アプリケーションであるBucky-Managementを使います。
Bucky-Managementは以下の5つのページが存在します。

* トップ
* テスト結果一覧
* テスト結果詳細
* テストスイート一覧
* テストスイート詳細

<img width="724" alt="スクリーンショット 2019-12-22 15.56.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/156024/aad7e8be-8401-0308-e352-dc03c2a0282b.png">


# テスト作成

それではBuckyでテストを作成していきます。
このようにyamlでテストケースを作成することができます。
Buckyの使い方に関してはこちらをご覧ください。
https://qiita.com/rueyjye/items/570ce17d698819f99091

```yaml
desc: トップ画面に関係するテスト
device: pc
service: bucky_management
priority: high
test_category: e2e
labels: TOP #ラベルを指定
cases:
  - case_name: top_1
    func: 右上のグラフのタイトル表示
    desc: 右上のグラフのタイトル表示の検証
    procs:
      - proc: open page
        exec:
          operate: go
          url: <%= ENV['BUCKY_FQDN'] %>/
      - proc: 右上のグラフのタイトルが「Latest Result e2e-pc」であること
        exec:
          verify: assert_text
          page: top
          part: title_e2e_pc
          expect: Latest Result e2e-pc
```

注目していただきたいのは`labels: TOP`の部分です。
ラベルを記入しておくことで、テスト実行時にオプションを指定すればこのテストケースのみ実行できるようになります。
上記のテストケースと同様に他のページでもテストケースを作成しておきます。
それぞれのテストケースにラベルも記入してあります。

```bash
.
├── result_detail.yml # テスト結果詳細
├── result_list.yml # テスト結果一覧
├── suite_detail.yml # テストスイート詳細
├── suite_list.yml # テストスイート一覧
└── top.yml # トップ
```

# GitHub Actions

CIはGitHub Actionsを使います。
作成したWorkflowは以下です。

```yaml
name: System testing

on: [pull_request]

jobs:

  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v1

    - name: Cache docker image of Bucky-Management
      uses: actions/cache@v1
      with:
        path: ~/caches
        key: ${{ runner.os }}-build-${{ hashFiles('Dockerfile') }}
        restore-keys: |
          ${{ runner.os }}-build-${{ hashFiles('Dockerfile') }}

    - name: Build and save docker image 
      run: |
        if [ ! -f ~/caches/images.tar ]; then
          docker build -t bucky-management . --build-arg rails_env=development && \
          mkdir -p ~/caches && \
          echo 'tar start'
          docker save bucky-management -o ~/caches/images.tar
          echo 'tar finish'
        fi

  run:
    needs: build
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v1

    - name: Cache restore
      uses: actions/cache@v1
      with:
        path: ~/caches
        key: ${{ runner.os }}-build-v3-after-${{ hashFiles('Dockerfile') }}
        restore-keys: |
          ${{ runner.os }}-build-v3-${{ hashFiles('Dockerfile') }}

    - name: Docker load
      if: steps.cache.outputs.cache-hit != 'true'
      run: | 
        docker load -q -i ~/caches/images.tar

    - name: Create docker network and run and setup BM
      run: |
        docker network create bucky
        docker-compose -f docker-compose.bucky.yml up --build -d
        sleep 10 

    - name: Migrate and insert test data
      run: |
        docker exec -i bm-app rails db:create
        docker exec -i bm-app rails db:migrate
        docker exec -i bm-app rails db:fixtures:load

    - name: Clone test codes
      run: |
        mkdir /opt/bucky-core 
        git clone https://github.com/hikimochi/bucky-testing.git /opt/bucky-core 

    - name: Run bucky-core
      run: |
        cd /opt/bucky-core
        docker-compose up --build -d 
        sleep 5

    - name: Execute testing
      run: docker exec -i bucky-core bucky run -D pc -d -l $(cat $GITHUB_EVENT_PATH | jq -r '.pull_request.labels[].name')
```

### jobs-build

Bucky-Managementイメージのキャッシュを作成しています。

#### Cache docker image of Bucky-Management

ここではBucky-Managementイメージのcacheを作っています。
テストを実行するときにプロダクトコードをマウントし、アプリケーションサーバを立ち上げる仕組みをとることでDockerfileを書き換えない限りは再ビルドしなくて済むようになります。

#### Build and save docker image

キャッシュヒットしないときはBucky-Managementコンテナをビルドし、特定のディレクトリにsaveしています。

### jobs-run

実際にテストを実行するジョブです。
GitHub Actionsでは`needs`を使うことでジョブの前後関係を作ることができます。

#### Cache restore

buildジョブで生成したcacheをリストアします。

#### Docker load

`docker save`されたimageをloadします。
machineのimages内にbucky-managementという名前でloadされます。

#### Create docker network and run and setup BM

Bucky-Managementを立ち上げるためのネットワークを作成し、docker-composeでアプリケーションを立ち上げます。
ウェブサーバとしてnginx、データベースとしてmysqlコンテナを使います。
`docker load`でbucky-managementイメージがローカルに展開されたので、`image: bucky-management`で[指定](https://github.com/hikimochi/bucky-management/blob/master/docker-compose.bucky.yml#L11)できるようになります。

#### Migrate and insert test data

データベースのマイグレーションとテストデータの格納を行います。
テスト実行の度にテストデータはきれいに作り直す仕組みをとっています。

#### Clone test codes

ここからはBucky-Coreでテストを実行するための準備を行っていきます。
テストコードをクローンします。

#### Run bucky-core

Bucky-Coreを実行するためにコンテナを起動します。
先程作成したネットワーク内で立ち上げることで、machineの影響を受けずcontainer_nameで[名前解決](https://github.com/hikimochi/bucky-testing/blob/master/docker-compose.yml#L16)できます。

#### Execute testing

実際にテストを実行する部分です。
`bucky run -l [ラベル名]`で指定したラベルを持つテストを実行できます。
`$GITHUB_EVENT_PATH`に関しては次で紹介します。

構成としてはこの様になっています。
<img width="668" alt="スクリーンショット 2019-12-22 15.06.29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/156024/234fb2b7-ce5c-946c-9d00-fa25a32e5dfc.png">

## GITHUB_EVENT_PATH

GitHub Actions以外のCIツールではPull Requestのラベル情報をとってくるにはGitHub APIを使う必要があります。
GitHub Actionsでは**GITHUB_EVENT_PATH**にそのビルドに関係する情報がjson形式で格納されます。
今回はその情報をjqで処理することで、ラベルの情報のみ取り出しています。
`cat $GITHUB_EVENT_PATH | jq -r '.pull_request.labels[].name'`

# 実際に使ってみる

ここではBucky-ManagementのTOP画面を改修したという想定です。
Pull Requestの作成時にラベルを付与します。
<img width="257" alt="スクリーンショット 2019-12-22 15.34.37.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/156024/6ba5fe66-a3f4-96ca-7a27-76c5d1c3ebde.png">
※本来のラベルとは違うラベルの使い方をしているのでラベルはテスト用だとわかるようにしたほうが良いと思います。例）「TEST: TOP」など

ビルドではこのように指定したラベルのみ実行されます。
<img width="946" alt="スクリーンショット 2019-12-22 15.40.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/156024/fcfd1072-dad3-4466-1584-1d19251ff3e0.png">
今回使用したコードはこちらで管理してますのでよかったら参考にしてください。

* [bucky-management](https://github.com/hikimochi/bucky-management)
* [テストコード(bucky-core)](https://github.com/hikimochi/bucky-testing)

# 今後の展望

今回はBucky-Managementをテスト対象としましたが、より大きな開発規模のものにおいては、QAと開発者の**ドメイン知識の差をラベルを通して埋める**ことにも繋げられると思っています。
プロダクトコードとラベルの対応をストアすることで、プロダクトコードが製品のどの領域（ラベル）に紐付いているかの情報が溜まっていきます。
QAは開発者に聞かなくてもある程度の情報はプロダクトコードとラベルの情報から得られることになります。
また、それを元に実行するテストを自動的に判別することも可能になります。

例えば、複数の開発者で修正が起こる`foo.rb`に対してラベルでの運用がある程度行われたとします。

|ファイル名|ラベル名|使用数|
|---|---|---|
|foo.rb|TOP|2|
||LIST|18|
||DETAIL|80|

このようなデータがある中で、ラベルが付与されていないPull Requestにおいてfoo.rbが改修されたときはDETAILのラベルがつくテストを実行する仕組みを作れば、修正ファイルに応じたテストの自動選択を実現することができます。
確率的に選べばより網羅的なテストができて良いかもしれませんね。

次回はこの仕組みを実際に構築した記事を書きたいと思います。

それでは良いお年を！