1
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

Workflowsでもcircleci tests split --split-by=timings使いたい

この記事はCircleCI Advent Calendar 2019の12日目の記事です。

CircleCIにはテストを並列実行させるためのコマンドが用意されています。
その中でも一番効率のよさそうなテストの実行時間をもとに分割して並列実行させるコマンドが、Workflowsとの相性が悪いためこれをなんとかした方法について紹介します。

CircleCI CLIとは

CircleCI CLIは多くの便利なツールを利用できるコマンドラインです。
今回はその中でも、テストファイルを分割するcircleci test splitコマンドについて説明します。

circleci test splitにテストファイルのファイル名リストを渡すと使用可能なコンテナ数で分割することができます。

$ circleci tests split test_filenames.txt

「使用可能なコンテナ数」はparallelismキーで指定されたものを参照しますが、分割数や分割後のどの組のファイルリストを出力するかを任意に指定することもできます。

$ circleci tests split --total=4 --index=0 test_filenames.txt

「タイミングデータに基づいた分割」のなにがうれしいか

circleci tests splitでの分割ルールには3種類あり、それぞれ以下のような特徴があります。

ルール 概要
ファイル名に
基づいた分割
テスト名によってテストをアルファベット順に分割
デフォルトのルール
ファイルサイズに
基づいた分割
ファイルサイズで分割
--split-by=filesizeオプションを付ける
タイミングデータに
基づいた分割
テスト実行後のstore_test_resultsデータよりタイミングデータを生成して実行時間で分割
--split-by=timingsオプションを付ける

タイミングデータに基づいた分割についてとてもわかりやすくまとめてある記事がCircleCI Advent Calendar 201824日目にあるので引用します :bow:

例えば今、テストファイルが7個あって、それぞれのテストにかかる時間が経験上「10秒、6秒、5秒、4秒、3秒、2秒かかる」ということがわかっているとします。
この場合、普通に1プロセスで実行すると10+6+5+4+3+2で30秒かかります。

ここで、CircleCIでparallelism: 3(3並列)で分割テストすることを考えます。

まず悪い例として「[10, 3], [6, 5], [4, 2]」と分割してみます。そうすると以下のようになり、一番遅いやつが13秒(10+3)なので全体として13秒かかることになります。

次に、それよりは少しましな例として「[10], [6, 3, 2], [5, 4]」と分割します。すると以下のようになり、一番遅いやつが11秒(6+3+2)ということになります。
つまり、分割の仕方によって全体のテスト時間が変わってしまう!(13秒 vs 11秒) ということになります。

タイミングデータに基づいた分割とは個々のテストにかかった時間をもとに、分割したテスト群それぞれにかかる時間をだいたい同じくらいになるように「いい感じ」に調整してくれるルールということです。

どうしてWorkflowsと相性が悪いのか

Workflowsを使っていない場合は.circleci/config.ymlファイルで1つのbuild job扱いになるため、store_test_resultsの結果が次のbuild jobに特に設定を意識することなく伝搬させることができます。
しかし、Workflowsを使っているとstore_test_resultsでタイミングデータが生成されても、次回の同じjobに伝搬してくれませんでした。

古い記事ですが、当時はWorkflowsでは使うことができないと回答がありました。
https://discuss.circleci.com/t/problem-using-split-by-timings-in-workflows/14780

キャッシュを駆使する

使えないと言われたからといって諦められませんw
幸いWorkflowsを使っていてもCircleCI CLIも使えるようですし、いろいろ調べてみたところタイミングデータを伝搬させるところだけが問題のようでした。
Workflowsはjobの下流と横へのcache受け渡しがいろいろできるのでそれらを駆使して考えてみました。

依存関係のキャッシュ_-_CircleCI.png

これがconfig.ymlだ

.circleci/config.yml
workflows:
  test_flow:
    jobs:
      - test
      - save_timing_data:
          requires:
            - test
jobs:
  test:
    ... # <- 環境準備

    parallelism: 3
    steps:
      - restore_cache:
          keys:
            - taiming-data-v1-{{ .Branch }}
      - run: mkdir ~/rspec
      - run:
          name: Run test
          command: |
            # circle-test-results/results.jsonがなかったらファイル名分割
            if [ -e "circle-test-results/results.json" ]; then
              TESTFILES=$(circleci tests glob "spec/**/*.rb" | circleci tests split --split-by=timings)
            else
              TESTFILES=$(circleci tests glob "spec/**/*.rb" | circleci tests split)
            fi
            bundle exec rspec -f RspecJunitFormatter -o ~/rspec/rspec.xml -- ${TESTFILES}
      - store_test_results:
          path: ~/rspec
      - run:
          name: Cache build number
          command: echo $CIRCLE_BUILD_NUM > .build_number
      - persist_to_workspace:
          root: ~/app # <- 設定したworking_directory
          paths:
            - .build_number

  save_timing_data:
    ... # <- 環境準備

    steps:
      - attach_workspace:
          at: ~/app # <- 設定したworking_directory
      - run:
          name: Get test metadata(timing-data)
          command: |
            mkdir circle-test-results
            curl "https://circleci.com/api/v1.1/project/github/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME/$(cat .build_number)/tests?circle-token=$CIRCLE_API_TOKEN" > circle-test-results/results.json
      - save_cache:
          key: taiming-data-v1-{{ .Branch }}
          paths:
            - circle-test-results

さいごに

こんな感じでWorkflowsでもタイミングデータに基づいた分割を使ってテストを効率よく分割して実行できるようになるはずです。
「はず」というのは、もともとテスト実行部分だけをAWS CodeBuildに投げてCIをまわすということをやったことがあるので、CircleCIだけで実現できるはずということで今回の記事にまとめてみました。

万が一、うまくいかないんだけど。。というのがありましたらご一報いただけると幸いです :bow:

引用・参考記事

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
1
Help us understand the problem. What are the problem?