CircleCI Advent Calendar 2018の4日目です🎄
3日目の昨日は @tomonorimatsumura さんの「CircleCIでOrbsを利用して並列処理を行う」でした。
はじめに
先日、Shallow Cloneに対応したリポジトリのチェックアウトができるGit Orbを公開しました。
Orbを作成するにあたって、「テストはどのようにすればよいのか?」ということは関心事のひとつです。
この記事ではCircleCIのドキュメントにあるテストのレベルと、Git Orbで使っているOrb Toolsを中心に、情報を整理してみます。
テストのレベル
公式ドキュメントのTesting orbsによると、Orbのテストには複雑さとスコープによって上がっていく4つのレベルが示されています。
- Schema Validation: OrbがYAML形式として正しいか、Orbのスキーマに準拠しているかをチェックします。CLIのコマンド1つで実行できます。
- Expansion Testing: 開発しているOrbの呼び出しを含む設定に対し、インライン展開する処理を行って意図した設定が生成されるかをテストします。これはCircle CLIのコマンドを組み合わせて確認できます。
- Runtime Testing: テストごとに必要なセットアップを行い、CircleCIのビルド内でテストを実行します。
- Integration Testing: 外部のサービスと実際に連携してテストします。連携先のテスト用環境が必要となります。このレベルが必要になるケースはコンテキストごとに手段が大きく異なりそうであることと、私自身まだノウハウが溜まっていないため、本記事ではこのレベルについては触れません。
レベル1と2は静的なチェックになるため、多くのケースではレベル3のRuntime Testing以上が必要になってくるでしょう。
テストのレベルごとのCircleCI CLIを使った例
上記のテストのレベルごとに、CircleCI CLIでどのようにテストできるかの例をまとめてみました。
これらを把握することで、後述のOrb Tools内部でCircleCI CLIによって何が行われているかの理解にも役立ちます。
Schema Validation
CircleCI CLIのcircleci orb validate
コマンドを実行することで、ファイルのチェックができます。
引数にYAMLファイルを渡します。
$ circleci orb validate ./src/orb.yml
標準入力からYAMLを受け取ることもできます。
$ cat ./src/orb.yml | circleci orb validate -
出力例
YAMLとしてパースできない場合
version: 2.1
description: This is sample.
commands:
hello:
description: Output 'Hello'
steps:
- run: echo 'Hello'
invalid_string
$ circleci orb validate ./src/orb.yml
Error: Unable to parse YAML
while scanning a simple key
in 'string', line 9, column 1:
invalid_string
^
could not find expected ':'
in 'string', line 10, column 1:
^
Orbとして構文エラーがある場合
version: 2.1
description: This is sample.
commands:
hello:
description: Output 'Hello'
step:
- run: echo 'Hello'
$ circleci orb validate ./src/orb.yml
Error: ERROR IN CONFIG FILE:
commands: hello: 0 subschemas matched instead of one
commands: hello: 2 schema violations found
commands: hello: required key [steps] not found
commands: hello: extraneous key [step] is not permitted
commands: hello: expected type: String, found: Mapping
Expansion Testing
CircleCI CLIのコマンドを組み合わせて、Orbをテスト用の設定ファイルにインライン展開し、その構造をチェックします。
次のOrbを例に説明します。
version: 2.1
executors:
default:
parameters:
tag:
type: string
default: "curl-browsers"
docker:
- image: circleci/buildpack-deps:<< parameters.tag >>
jobs:
hello-build:
executor: default
steps:
- run: echo "Hello, build!"
この段階でcircleci orb validate
コマンドによるSchema Validationも行っておきましょう。もし問題があった場合は詳細なエラー結果を得ることができます。検証が通ったらdevelopmentバージョンとして公開します。
$ circleci orb publish src/orb.yml namespace/orb@dev:0.0.1
このOrbを利用するconfig.yml
を用意します。
version: 2.1
orbs:
hello: namespace/orb@dev:0.0.1
workflows:
hello-workflow:
jobs:
- hello/hello-build
circleci config process
コマンドをconfig.yml
に適用して、Orbをインライン展開させます。
$ circleci config process .circleci/config.yml
展開した結果は次のようになります:
version: 2
jobs:
hello/hello-build:
docker:
- image: circleci/buildpack-deps:curl-browsers
steps:
- run:
command: echo "Hello, build!"
workflows:
hello-workflow:
jobs:
- hello/hello-build
version: 2
# Original config.yml file:
# version: 2.1
#
# orbs:
# hello: namespace/orb@dev:0.0.1
#
# workflows:
# hello-workflow:
# jobs:
# - hello/hello-build
このレベルのテストでは、期待通りに値が展開されているかをチェックします。
例えば、Dockerイメージのタグがデフォルト値として指定したcurl-browsers
になっているかを確認するために、yqコマンドを使って次のように取り出すことができます。
$ circleci config process .circleci/config.yml | yq read - 'jobs.hello/hello-build.docker[0].image'
circleci/buildpack-deps:curl-browsers
テストを自動化する場合は、この出力をアサーションすればよいでしょう。
Runtime Testing
このレベルではOrbを用いた設定ファイルをcircleci local execute
コマンドを使って実行します。circleci local execute
がDockerを利用しているため、CircleCIのジョブの中で実行する場合はmachine
executorを用いる必要があります。
$ circleci local execute -c test/sample.yml | tee output.log
デフォルトは.circleci/config.yml
を参照しますが、-c
オプションでファイル名を指定できるので、テストケースごとにファイルを作っておけます。tee
コマンドも併用して出力結果を保存しておき、次のステップやpost-step
でgrep
コマンド等を用いて出力のアサーションを行うとよいでしょう。
注意点
circleci local execute
コマンドの大きな注意点として、Workflowに対応していないことが挙げられます。つまり、そのままではOrbのJobのテストが行なえません。
そこで、build
という名前のジョブに置き換えることで実行できるようにします。
まず、Workflowに定義したジョブにname
パラメーターを使ってbuild
という名前を付与します。
version: 2.1
orbs:
hello: namespace/orb@dev:0.0.1
workflows:
main:
jobs:
- hello/hello-build:
name: build
そして、circleci config process
コマンドで処理すると、名前がbuild
のジョブに置き換わります。
version: 2
jobs:
build:
docker:
- image: ...
steps:
- run:
...
workflows:
main:
jobs:
- build
version: 2
これでcircleci local execute
で実行できるようになりました。Workflowの定義は残っていますが、build
という名前のジョブさえいれば無視されます。
※ この方法はCircleCI Orbs 入門 | tsub's blogを参考にさせていただきました。
Orb Toolsを使ったテスト
実際にCircleCIでOrbのテストを実行する場合はCircleCI CLIをラップしたOrb Toolsを使うのが便利です。
ここではGit Orbで行っているテストをベースに説明します。
orb-tools orbの呼び出しを追加
Orb自身の.circleci/config.yml
にorb-tools orbを使った自動テストの定義を追加していきます。
version: 2.1
orbs:
orb-tools: circleci/orb-tools@volatile
公開用に1つのYAMLへパッキング
Orbのソースコードは分割してOrbの構造に通りにファイル配置しておくと、circleci orb pack
コマンドで1つのYAMLファイルへパッキングできます。
このファイル配置はartifactory orbのソースコードを参照するとわかりやすいです。
次のようにファイルが配置されているとします。
src/
├── @orb.yml
├── commands
│ ├── command-1.yml
│ └── command-2.yml
├── executors
│ └── executor-1.yml
└── jobs
└── job-1.yml
src
直下の@orb.yml
の中身は次のようになっていることとします。
version: 2.1
description: |
This is sample Orb.
この状態でcircleci config pack src/
を実行すると次のYAMLが生成されます。(キーはアルファベット順にソートされます)
commands:
command-1:
# src/commands/command-1.ymlの中身
command-2:
# src/commands/command-2.ymlの中身
description: |
This is sample Orb.
executors:
executor-1:
# src/executors/executor-1.ymlの中身
jobs:
job-1:
# src/jobs/job-1.ymlの中身
version: 2.1
pack jobではCircleCIのジョブとして扱うのに便利なオプションを付けてcircleci orb pack
コマンドを実行することができます。
workflows:
version: 2
test-and-publish-to-development:
jobs:
- orb-tools/pack:
source-dir: src/
destination-orb-path: packed/orb.yml
workspace-path: packed/orb.yml
artifact-path: packed/orb.yml
validate: true
source-dir
にOrbのソースコードが格納されているディレクトリを指定し、destination-orb-path
にパッキングした結果の出力先を指定します。
workspace-path
はWorkspaceに永続化するファイルを指定します。ここではパッキングしたOrbをWorkflowの次のジョブに渡すために使っています。
artifact-path
にはArtifactsに保存するファイルを指定します。パッキングしたOrbをしてしておくことで、ジョブのArtifactsから参照することができます。
validate
にtrue
を設定しておくとcircleci orb validate
コマンドによるチェックも実行してくれます。
テスト用の設定ファイルごとにテストを実行
test-in-builds jobとlocal-test-build commandを組み合わせて使うことで、あらかじめ用意したconfig.yml
相当の設定ファイルと開発したOrbをパッキングし、Orbをインライン展開したものをcircleci local execute
コマンドで実行してくれます。
- orb-tools/test-in-builds:
requires: [orb-tools/pack]
attach-workspace: true
orb-location: packed/orb.yml
orb-name: git
test-steps:
- orb-tools/local-test-build:
test-config-location: test/shallow-clone-checkout.yml
- orb-tools/local-test-build:
test-config-location: test/shallow-clone-checkout-with-https.yml
attachment-workspace
をtrue
に設定して前のジョブでパッキングしたOrbを受け取り、orb-location
にそのパスを指定します。
orb-name
にはテスト用に用意したconfig.yml
相当の設定ファイルからOrbを参照する名前を指定します。
テスト用設定ファイルは具体的には次のようになります:
version: 2.1
orbs:
cli: circleci/circleci-cli@volatile
jobs:
build:
executor: cli/default
working_directory: ~/test-shallow-clone-checkout
steps:
- git/shallow-clone-checkout
steps
に開発したOrbのcommandであるgit/shallow-clone-checkout
を指定していますが、Orb自体を呼び出す定義はこのファイルにはありません。orb-tools orbのlocal-test-build command内でcircleci config pack
コマンドとcircleci config process
が実行され、Orbがインライン化されます。そしてインライン化された設定ファイルがcircleci local execute
コマンドで実行されます。
local-test-build commandにはtest-config-location
にテスト用設定ファイルを指定する他に、should-fail
にtrue
を指定することでエラーとなるパターンのテストも行えます。また、external-check-steps
に追加で検証したいコマンドをステップとして指定することもできます。これはパラメーターが期待通り展開されているかを厳密にチェックしたいときに使えるでしょう。
developmentバージョンとして公開
テストが済んだらpublish jobを使ってdevlopmentバージョンを公開し、他のリポジトリ等から手動テスト可能な状態にしておきます。内部的にはcircleci orb publish
コマンドが実行されます。
- orb-tools/publish:
requires: [orb-tools/test-in-builds]
orb-path: packed/orb.yml
orb-ref: "ganta/git@dev:${CIRCLE_BRANCH}-${CIRCLE_SHA1}"
publish-token-variable: "${ORB_PUBLISHING_TOKEN}"
validate: true
checkout: false
attach-workspace: true
orb-path
に公開したいOrbファイルのパスを、orb-ref
にdevelopmentバージョンとしてのリファレンス名を指定しています。タグをCircleCI内で参照できる環境変数を使って、dev:#{CIRCLE_BRANCH}-${CIRCLE_SHA1}
のようにしておくとわかりやすいです。
publish-token-variable
にはCircleCIにアクセスするためのトークンを指定します。User settingsのPersonal API Tokensからトークンを発行し、開発しているOrbのCircleCIプロジェクトのEnvironment VariablesにORB_PUBLISHING_TOKEN
という名前で登録しておくと、上記の記述で参照できるようになります。
Workspace経由でOrbのコードを受け取って公開しており、このジョブでのリポジトリのチェックアウトは不要なためcheckout
はfalse
としています。
最終的なconfig.yml
最終的に.circleci/config.yml
は次のようになります:
version: 2.1
orbs:
orb-tools: circleci/orb-tools@volatile
workflows:
version: 2
test-and-publish-to-development:
jobs:
- orb-tools/pack:
source-dir: src/
destination-orb-path: packed/orb.yml
workspace-path: packed/orb.yml
artifact-path: packed/orb.yml
validate: true
- orb-tools/test-in-builds:
requires: [orb-tools/pack]
attach-workspace: true
orb-location: packed/orb.yml
orb-name: git
test-steps:
- orb-tools/local-test-build:
test-config-location: test/shallow-clone-checkout.yml
- orb-tools/local-test-build:
test-config-location: test/shallow-clone-checkout-with-https.yml
- orb-tools/publish:
requires: [orb-tools/test-in-builds]
orb-path: packed/orb.yml
orb-ref: "ganta/git@dev:${CIRCLE_BRANCH}-${CIRCLE_SHA1}"
publish-token-variable: "${ORB_PUBLISHING_TOKEN}"
validate: true
checkout: false
attach-workspace: true
今回はOrb Tools自身のconfig.ymlを参考に書いてみたのですが、test-in-builds jobではExpansion Testingで行ったようにpublishしたdevelopmentバージョンのOrbを参照するのではなく、Orbのコードをそのままインライン化していた点がわかりづらかったです。
publish jobを先にしてdevelopmentバージョンを公開し、それを参照するテスト用の設定を用意し、test-in-builds jobを経由せずに直接local-test-build commandを呼び出した方が実際に利用される状態に近くなるため、テストとしてはよいかもしれません。
(参考) Batsを用いたテスト
BatsはBash向けのテスティングフレームワークです。Orbの確認に便利なアサーションを作成してテストの可読性を高めることができます。artifactory orbのtests配下を覗いてみると面白いです。
アサーションに用いる関数をヘルパーにがんばって定義しているので、この辺を再利用できる仕組みができると便利になりそうです。
ただし、オリジナルのBatsは2013年以降積極的にメンテナンスが行われていないため、Bats-coreを用いたほうがよさそうです。
まとめ
まだまだ過渡期でテストの手法が固まっていませんが、逆にそれを確立していくところを体験できるのは楽しいですね。
現状ではやはり公式のOrb Toolsが一番お手軽だと思います。
明日のCircleCI Advent Calendar 5日目は @skzwksk さんです。