Kengo's blog

Technical articles about original projects, JVM, Static Analysis and TypeScript.

Cloud RunがImage not foundと言ったときの原因事例集

Cloud Runはとても便利なのですが、エラーメッセージがわかりにくいことがあります。特にデプロイしたときに遭遇するこちらのエラーが厄介です:

ERROR: (gcloud.run.deploy) Image 'asia-northeast1-docker.pkg.dev/foo/bar/baz' not found.

コンテナを引っ張ってこれなかったときは原因が何であれこのメッセージだけ残してデプロイが失敗するので、自分で切り分けなければなりません。ここ数ヶ月で複数の原因に遭遇したので記録を残します。

指定したタグを持つコンテナがない

docker build はしたけど docker push はしてなかったケース。見つからなかったのは指定したタグを持つイメージなのですが、エラーにはアクセス先レジストリそのものが見つからなかったかのように出てくるので要注意です。

Cloud Run Service Agentに権限がない

公式にはこちらのページで説明されています

project-aのCloud Runから他のプロジェクトproject-bにあるArtifact RegistryやContainer Registryからコンテナを引っ張ってくるときに、project-aのCloud Run Service Agentに適切な権限がないと失敗します。なおCloud Run Service Agentは service-PROJECT_NUMBER@serverless-robot-prod.iam.gserviceaccount.com という名前を持っています。

Artifact RegistryであればArtifact Registry Readerロールが、Container RegistryであればStorage Object Viewerロールが必要になります。

イメージのマニフェストがおかしい

docker/build-push-action を導入したときに遭遇しました。docker/setup-buildx-actionと組み合わせると application/vnd.oci.image.index.v1+json media typeを持つイメージにタグが打たれるようになり、これをCloud Runは扱えないようです。イメージが使えるものだということは docker run で確認したし、公式によるとOCI image formatはサポートしているようなのですが。

docker/build-push-action 単体だと docker build 時と同様に application/vnd.docker.distribution.manifest.v2+json media typeを持つイメージにタグが打たれるため、Cloud Runでも扱えるイメージができます。

2023/01/23更新

こちらはbuildx 0.10の挙動変更による影響でした。

github.com

対策としては docker/build-push-action の設定に provenance: false を追加することが推奨されています。

Maven4の動向メモ

Maven 4は2年近く前から開発されていて、最近alphaバージョンがリリースされています。自分がMavenを使うことは殆ど無いとは思うのですが、傾向だけでも把握しておきたくてリリースノートやメーリングリストに潜ってみました。

ざっと見た感じ、Mavenでマルチモジュールビルドをしている人や、Maven向けにプラグインや拡張を提供している人は動向を追ったほうが良さそうです。既にGradleに移行した人はあまり気にしなくて良いでしょう。

マルチモジュール周りの改善

以下のサイトで解説されています。他にも親POMのバージョンを記入しなくて済むとか、依存解決周りの改善が多そうです。

maarten.mulders.it

プロジェクト定義に変更を加えつつ、古いMavenからも使えるようにしたい

MNG-6656動画で基本設計が説明されているようです。POM 4.0.0の配布を継続しつつ、ビルド時には新しいプロジェクトオブジェクトモデルを使いたいようですね。ただXSDファイルはまだ https://maven.apache.org/xsd/ では公開されてなさそうでした。

Gradleが配布時にPOM 4.0.0に加えてメタデータを添付しているのと逆で、POM 4.0.0の定義に収まるように情報を削ぎ落として互換性を確保する感じと理解しました。POM 4.0.0という枠を維持するということは、例えばGradleがcompileをapiとimplementationに分けてABIの概念を持ち込んだような破壊的な変更はあまり考えていなさそうですが、少なくとも新機能を入れることはやりやすくなるでしょう。

Maven Wrapper

4.0で wrapper ライフサイクルが導入され、 mvn wrapper:wrapper ではなく mvn wrapper で実行できるようになるようです。 Maven Wrapper 3.1.1 のドキュメントに以下の記載がありました:

wrapper:wrapper is the default goal, invoked during the wrapper phase in Maven 4. It downloads and unpacks the maven-wrapper distribution,

ちなみに「3.7.0から入る」という古い情報もありますが、リリースノートによると3.7.0は廃止になって3.7の新機能は4.0に入れることになったそうです。

とはいえMaven 3には wrapper ライフサイクルが無いだけで wrapper プラグインの公式化は完了しています。よって mvn wrapper:wrapper がMaven3でも既に通りますので、実用上の問題はないと思います。

古い”API”のサポート中止

Maven 3.0 またはこれよりも古いバージョンのAPIを利用したプラグインのサポートをやめたい、という話が出ているようです。

github.com

個人的にはMavenにAPIなんて今までなかったよねという話がちょっと面白かったです。maven-plugin-apiというパッケージは存在するのですが、ちょっと複雑なことをするとすぐに実装詳細であるmaven-coreに依存する必要性が出てくるという話ですね。

実際alphaリリースには既存のプラグインが動かなくなる問題も報告されていて、publicなクラスを積極的に削除しているのは間違いないようです。プラグインや拡張を提供している人は、alphaはともかくbetaとかRelease Candidateとかが出てきたタイミングで動作確認をしたほうが良いかもしれません。

パッケージなメガベンチャーから医療機関向けサービスを提供するスタートアップに転職して6ヶ月して感じたこと

いよいよ試用期間が終わりまして、ドメイン知識はともかく同僚の働き方はだいぶ掴めてきた気がします。6ヶ月何をやっていたかは会社の方のブログに書いたので、こちらでは感じたことを書いておきます。なお入社2ヶ月時点での所感を別の記事に記載しています

文脈

  • 人事給与,会計や購買管理といったERPをメインとしたパッケージベンダーから、医療機関向けERPを提供するスタートアップに転職した
  • SaaSを製品ラインナップに加えようという活動に従事した経験があるので、パッケージとウェブサービスと両方それなりに知っているつもりだった
  • 製品開発、製品運用、プロジェクトマネジメントないしピープルマネジメントはわりとわかっているつもりだった

ウェブサービスとパッケージはやはり大きく違うという話

ウェブサービスのほうがサポートバージョンを絞って効率よく開発できる、動作環境を掌握できるので細かいサポートが不要になるといった違いはまぁ知っていたとおりでした。古いミドルウェアやOS、明らかに少ないメモリみたいなパッケージあるあるに心を砕く必要性が少なくなったのはとてもやりやすいです。

ログやメトリクスを採ることのハードルが下がったのもやりやすく、仮設が検証しやすいということは即機能の成長速度に反映されるわけで、製品づくりのハードルも大きく下がったと思います。

一方で各お客様固有の環境は引き続き存在しています。医療機関はその内部に複雑なネットワーク構造を持っていることが多いようで、お時間のある方はぜひ事例がいくつか載っている月刊新医療 2022年11月号を見ていただきたいのですが。こういった多様かつ複雑なユーザ事情を踏まえてサービスを提供するのは難しいなと思う機会が多いです。

しかも連携先システムが多い!既存システムはもちろん、オンライン資格確認や電子処方箋のような新システムもどんどん入ってくるわけで、これらの考慮がいるという点で純粋なウェブアプリケーションではありません。こうした固有環境を織り込んでのサービス信頼性向上は、パッケージ屋としての経験が活かしやすい部分かもしれません。

医療機関の情報セキュリティ基準はだいぶ明確

医療機関といえばランサムウェアに狙われている印象がありますが、国から出ている情報セキュリティ関係のガイドラインはわりと明確です:

  1. 医療情報システムの安全管理に関するガイドライン 第5.2版(令和4年3月)
  2. 医療情報を取り扱う情報システム・サービスの提供事業者における安全管理ガイドライン

明確なら実行も簡単というわけではもちろん無いのですが、ゴールや判断基準が明確であればベンダーとしてのお客様とのコミュニケーションはやりやすい部分があると思っています。

ここで面白いのが、この「3省2ガイドライン」と呼ばれる2つのガイドラインはそれぞれ「医療機関」と「医療情報システムを開発/運用する情報処理事業者」とを対象にしている点です。つまり3省2ガイドラインに準拠した医療を提供するには、医療機関とベンダーの双方が継続的に協力しないといけないということですね。 情報セキュリティ周りには取得して何が守られるんだっけみたいな認証とか違反が明らかになっても剥奪されない認定とかが跋扈している印象がどうしても拭えないのですが(個人の感想です)、このある種の緊張感あるガイドラインは結構好きです。

ちなみに医療機関向けセキュリティ教育支援ポータルサイトも整備されてきていて、国も然るべき手をきちんと考えて打っているのだなと思わされます。数年後は医療機関の情報セキュリティに対する印象も大きく変わっているかもしれませんし、微力ながら変えるための一翼を担えればと思います。

mhlw-training.saj.or.jp

採用が活発

組織は人、人は採用からということで採用にめっちゃ力を入れているわけですが、まさか入ってすぐここまで駆り出していただけるとはという驚きがありました。日常的な採用プロセスはもちろん、インタビュー記事だけでも2回も出していただいています:

実際JVMやバイトコードの酸いも甘いもじっくり10年超見てきた自分がSREやってますというのはサーバサイドKotlinを推す企業の採用戦略としては非常に正しいと思うので、今後も貢献できればとは思っております。実際やりたい・やるべき施策も大量にバックログに積んであって、当面は忙しそうだなという感じです。

あとは社内外の勉強会も活発で、自分が直接関わったものだけでも6回くらいやりましたね。会社ブログに情報が出ているのは以下です:

おかげさまで他社様とのコラボレーション企画も多く、こういうフットワークの軽さと顔の広さはすごくいいなと思っています。VPoEの弛まぬ尽力の賜ですね。自分自身も他者に声をかけられる・かけていただける開発者でありたいものです。

シニアだけ採用する != 教育不要

さすがにGitコマンドの使い方とかテストコードを書こうねみたいな意識付けとかは要らないんですが、シニアだから教育不要ということは全く無くて、むしろシニアだからこそ新たな学習機会を求めるのですよね。自分のバックグラウンドは他の社員と比べてやっぱり異色なので、ドメイン知識や技術を学びつつ自分からも還元できればと思っています。

とりあえずKotlin Fest 2022で話したようなDetektの話とか、結構突っ込んで研究してきたGradleの話とかは役立てていただけているように思います。特にGradleやGitHub Actionsはちょっとした工夫でぐんとビルド時間が短縮されるので、開発体験の向上に貢献できているかなと。15-20分かかっていたワークフローを12-15分くらいに短縮して、でも5分くらいにしたいよねっていう話をしているのが今です。頑張ります。

それはそれとして、才能ある新入社員と継続的に話して成長を支援することを続けてきた自覚はあるので、そのうち新卒採用もできると嬉しいです。新卒で入ってきてくださった方のほうが自分よりもめっちゃ優秀、みたいな体験があるとやはり緊張感出ますし、お客様の課題を解決するのとはまた違った達成感もありますし。

肩書に助けられてたんだなぁという話

前職では上司と駐在員という肩書があったので、新しく入ってきた”部下”との関係構築が比較的やりやすかったように思います。一方で新しい職場はフルフラットなのでこうしたバフやバイアスが存在せず、日頃から関係を構築しておかないというべきことも言いにくい(言えない空気はないんだけど「お前何様だよ?」って自分で思っちゃう)のだなという新鮮な発見がありました。自分が相手の”上司”であるという責任感にけっこうバフもらってたんですね。

6ヶ月あればさすがに言い方がわかってきて、相手が誰であろうと自分の弱さや専門性をどんどん出していこう感は出てきているのですが、「人間を信用しないで!」とか「KILL ALL HUMANS」とか叫んでるとさすがに自分でも「おっ、サイコパスか?」とは思いますね。

技術的ディスアドバンテージの埋め方

TerraformとGolang、GCPは入社してからのキャッチアップでした。GCPはQwiklabsがあるのでまだ楽でしたが、Terraform周りは自分がまだあまり読めないGolangで書かれていてけっこう難しく、大きめの失敗をいくつか経験しました。副業で入ってきていただいてる詳しい方に何度もサポートいただいて助かりました。

当たり前なんですが、詳しい方にどんどん聞く、わからないことをわからないと叫ぶことはとても大事だなと思います。Working Out Loud風の働き方は今後も継続していきたいです。

まとめ

土地勘がついてきてどういった貢献ができるのか・誰に聞けばわかるのかが見えてきた感じです。組織の規模や形が変わったことで自分の動き方も変える必要が出てきたところと、今までの強みが活かせるところとを理解して動いていきたいです。

2022年に試した開発ワークフロー関係の機能やツール

数えてみたら意外と数あったのでまとめます。

release-please

Google謹製のリリース自動化ツール。monorepo対応のRelease Drafterという感じですが、リリースはDraft Releaseの安定版への昇格ではなく、PRのマージによって行います。PRでリリースするという点ではgit-pr-releaseぽいですが、ブランチは main だけでリリースブランチは無い感じ。changesetsよりはとっつきやすい印象です。

github.com

例えば↓のようなワークフローを用意すれば、モジュールごとにGitHub Releaseを作成するためのPRを自動作成できます。 初期セットアップでJSONファイルを2つ作る必要があるのが若干面倒ですが、それさえ越えてしまえば考えることは少なさそうです。

# .github/workflows/release-please.yml
on:
  push:
    branches:
      - main

name: release-please
jobs:
  release-please:
    runs-on: ubuntu-latest
    outputs:
      # workspaces/module_1 ディレクトリにモジュールがある想定
      module_1: ${{ steps.release.outputs['workspaces/module_1--release_created'] }}
    steps:
      - uses: google-github-actions/release-please-action@v3
        id: release
        with:
          command: manifest
          token: ${{ github.token }}
          default-branch: main
          monorepo-tags: true
  module_1:
    needs: release-please
    if: needs.release-please.outputs.module_1
    runs-on: ubuntu-latest
    steps:
      # リリース用の設定

なおrelease-please以外のリリース自動化ツールについても以前まとめてますのでよろしければ。

tfaction

めちゃくちゃ便利ですね。自分としてはTerraform使うなら必携という感じです。安全安心を計算能力で買う感じのアプローチでActions代がちょっとかさむ印象はありますが、ワークフローを工夫すれば改善できる余地がある気もしています。

suzuki-shunsuke.github.io

スクリプトの塊なので全体像の把握は難しいですが、設定を展開する部分と処理を実行する部分に別れているのだとわかればやりやすいです。GCP民はコントリビュートチャンスいろいろありそう。

version catalog (Gradle)

複数プロジェクトで利用されている依存をまとめて管理するためのファイル。GradleにもTomlが来るとはねぇという感じですが、Yamlより書きやすいのは間違いないので特に問題はないです。Kotlinのようなプログラミング言語で書く必要性はなかったと思いますし。

docs.gradle.org

例えばbuildSrcとメインのプロジェクトの双方でspotlessを使うとDependabotがPRを2つ作っちゃう問題があるのですが、version catalogを使えばきれいに解決できます。現時点ではDependabot自体がversion catalogに対応していないという問題はあるので、Renovateに切り替える必要はありますが。

あとKotlin DSLだとextra propertyやプロジェクトプロパティでの依存バージョン管理をするのがやりにくいので、Gradle 8.0以降でKotlin DSLが標準になることを踏まえても今後version catalogがメジャーになるんだと思います。

composite action (GitHub Actions)

何度か出てくる似たような処理を抽象化してinputsで処理を分けられるようにするというやつ。 複数環境にデプロイするようなワークフローとか、ちょっと凝った初期化処理が必要なワークフローとかで使いました。

https://docs.github.com/en/actions/creating-actions/creating-a-composite-action

reusable workflow (GitHub Actions)

何度か出てくる似たような処理を抽象化してinputsで処理を分けられるようにするというやつ、その2。 自分のケースだとまだ使い所がないというか、Environmentsと合わせると便利なんだろうなという理解で止まっている。

https://docs.github.com/en/actions/using-workflows/reusing-workflows

--mount=type=cache (Docker)

MavenやGradle、aptなどのインストール処理はキャッシュがあると早い。が、コンテナ環境にローカルの ~/.m2/repositories とかをCOPYするのはハードルが高い。というときに使いやすそう。

docs.docker.com

ただローカルでは恩恵を受けやすいのだろうけど、Circle CIではまだって感じらしい。ここを頑張るよりはコンテナの外でjarを作ってCOPYする方がキャッシュ容易性という点ではきれいだし、CI環境が宣言的にメンテされてればビルドの再現性という点でも充分なので、自分で使うことはあんまりないんじゃないかという気がしている。

--mount=type=secret (Docker)

GCPのApplication Default Credentialをコンテナに渡したい、というときに使えると教えてもらったやつ。 コンテナの中で ./gradlew build するときにGCSをGradle Remote Build Cacheとして利用したい、みたいなときに使った。

docs.docker.com

これもやはりコンテナの外でjarを作るやり方なら不要だけど、 type=cache よりは使いやすそう。

2022年のOSS活動状況まとめ

昨年のに引き続きOSS活動状況をまとめます。2022年12月20日時点の情報です。

概要:OSS活動は減った

GitHubのプロファイルページによると今年は4,513 contributionsでした。ただこれはprivate repoでの活動を含む値で、これを除くと1,263 contributionsと昨年や一昨年よりも少ない数値になりました。7月に転職してから新しい仕事にキャッチアップしていてあまり時間が作れていないのもありますが、Javaを書かなくなっていよいよSpotBugsを使わなくなったことも大きいです。

Private Repoの貢献を含まない値
Private Repoの貢献を含む値

新しい貢献領域を探さないとなとは思っていますが、ここ6年間でOSS活動から受けたベネフィットはあんま無いなというのが正直な感想なので、焦らず食指が動く領域に出会うのを待つのもいいかなという気持ちもあります。

とはいえこういう焦りも無いことはない(出典 まんがタイムきらら編集部

その他

ひきつづき自分が困った時に関連GitHub ActionにPRを出していました。

医療向け基幹システムを提供するスタートアップに入社しました

前回の退職エントリに続けての入社エントリです。

2022年7月1日より株式会社ヘンリーにSREとして入社しています。Java→Kotlin、AWS→GCP、Ansible→Terraformなど技術的には大きな変化を伴う転職でしたが、SREとしての観点や課題解決プロセスへの習熟、そして持ち前のひとがらのよさ(なぜか変換できない)などのポータブルなアビリティに助けられて2ヶ月生きています。このエントリーでは入社の目的と2ヶ月働いてみての転職の感想を残しておきます。

目標

新卒入社のころと違って自分なりの働き方やプロフェッショナルとしての在り方があるためそこまで厳密な目標は定めなくていいのかなと思う反面、振り返りや内省が成長の糧になることも確かなので、3つほど定めています。

1. 顧客と開発現場から学び、製品とチームを継続的に改善する

さすがに14年も経つと、ある程度はITエンジニアとしての基礎や専門性に自信がついてきます。その実態は、自信を持たないといけないという焦りかもしれませんが。。。

ともかく、継続的に仮説を検証して製品やチームに反映させることを通じて成長を積み重ねることや、その実行のための各種棚卸しや優先度付け、情報を整理したうえでの議論などはある程度可搬性のある技術として身についた気がしています。また昔「ソフトウェアを育てるにはユーザが多いほうがいい」と考えていたのとは違うやり方が多数あるということもSaaSやリーンソフトウェア開発手法の文脈で見てきたので、あまり規模にこだわることは本質的ではないのだなという気づきも得ました。ので前回の目標1を単純化して、継続的な学習の実行を新しい目標としています。

2. 製品を通じて社会の課題を解決する

今回転職活動を通じて多くの会社さんと話す機会を作っていただいたのですが、その中には全くときめかないところがいくつかありました。大きく分けると2パターンあり、自らを破壊的イノベーターと位置づけながら「なぜ新技術によってレガシーのやり方を駆逐できるのか」を説明できないところ、「新しいアプローチによって顧客事業を助けられる」と売上機会を説明するだけで「どのような社会的課題を解決するのか」を語ってくれないところには魅力を感じませんでした。

これは新卒で入った会社がブレークスルーによる社会的課題の解決にこだわっていたことの影響だと分析しています。新卒で入る会社が発想や働き方に強く影響するというのは本当なのかもしれません。いまの私は「新しい技術」だけでも「新しいアプローチ」だけでもない、「新しい技術で新しいアプローチを実現することで難しい社会的課題を解決する」働き方がしたいようです。

また同様の理由から、コンサルや請負開発のような自社で製品を持たない働き方を本業にするイメージは湧きませんでした。CD最適化や静的解析、ビルドツールといったスキルだけ見ると、むしろ多数のプロジェクトを実装して渡る働き方はマッチしているとは思っているんですけどね。Canは合ってもWantが合わないという感じなのでしょうか。

3. ドメインの課題を解決する手段とプロセスを理解する

これは俗に言う「チャレンジ目標」です。

私はいままでたくさんの部署や役割を転々とさせていただいて、製品開発だけでなく保守や研究開発、管理統制といった経験を積むことができました。また製品種別だけでもBtoB、BtoC、BtoE、社内ツールと多岐にわたる経験ができました。

が、一方でひとつのドメインに長く留まって業務課題を解決することはしてきませんでした。よく言えば多彩な観点からソフトウェア開発を支えられる人材ではあるのですが、前述したとおり社会的課題の解決に価値を感じている以上、ソフトウェア開発の手前つまり「ドメインの課題を噛み砕いて実行可能な計画に落とし込む」スキルを自分で持っておきたい気持ちもありました。

このスキルを得ることはソフトウェア開発者としての成長にもつながると思っています。ドメイン駆動開発の話です。出典を失念したのですが「昔は言語の表現力や計算機の性能が不足していたのでGoFのようなデザインパターン技法が注目された。現代では多くの障害が取り除かれた結果、変化するソフトウェアによる課題解決という核心に資するドメイン駆動デザインパターンが注目されている」という表現を読んで非常に納得できたことがあり、以降システム設計や保守に加えてドメイン駆動についても学んでいます。

今回の転職で「ドメインの課題を噛み砕いて実行可能な計画に落とし込む」ことを学び、観察し、実践する機会を多く得られそうなので、業務を通じて理解を深めていきたいと考えています。

入社2ヶ月時点での所感など

1. 異業界スタートアップで働いてみて

大企業向け基幹システムを提供するメガベンチャーから医療機関向け基幹システムを提供するスタートアップに転職したわけですが、業務内容自体は意外と変わりませんでした。少なくともベンチャー・スタートアップでは、自分で課題を見つけて周囲を巻き込んで解決していく「問題解決の核」としての働き方はかなりポータブルなんだなと感じます。

業界が変わって大きく違ったのは顧客の求めるものでしょうか。前職だとROI(情報投資効率)がひとつの大きな指標だったのですが、医療機関の電子カルテやレセプトコンピュータ(レセコン)だとまた違った話になるようです。またユーザがかなり多様なため、業務想定やユーザ体験設計などの難度が一段上がった気がします。

2. OSS開発ないし専門性と直接の関係がない役割へ転職して

静的解析関連のOSSを長年やっていることもあり静的解析サービスの企業はよく検討していたのですが、ビジネスモデルの問題かどうしても魅力的なポジションがなく、ここは早い段階で諦めていました。 そのため静的解析と直接の関係がない会社に入ったことは特に気にしていません。

またヘンリーはOSSを積極的にはやっていませんが、OSS経験があることは評価してくれていましたし、実際ここ2ヶ月でgradle-gcs-build-cache, tfmigrate, tfaction, gcs-proxy-cloud-runといくつかの貢献を業務として行えました。のでこれも気にせず正解だったと思います、まぁ前職でも言うほど業務ではOSSやってなかったですし。

3. 駐在員としての経験がフルリモートにマッチしているかも

オンライン会議前にアジェンダをまとめておくとか、とにかくドキュメントに落とすとか、カレンダーに不在予定含めすべてのスケジュールを入れておくとか、ソフトだが回りくどくない表現をするとか、Working Out Loudするとか。駐在員として本社の人とどうコミュニケーションするかを突き詰めた結果としてのコミュニケーションスキルはフルリモートで遺憾なく発揮されています。

ちょっと面白いのが「戸田さんはエンジニア以外にもやってることがわかりやすい」「コミュニケーションがいちいち工夫されていて、フルリモートのはずなのに存在感がすごい」というフィードバックをいただくことですね。工夫って……Slack書いてるだけだが?というのは置いといて、もはや意識的にやっていることではなかったので新鮮でした。

VPoEお墨付き↑の存在感🤗

まとめ

とりあえず2ヶ月を走りきり、今後もやれそうな感覚を掴めています。やるべきこと・やれることが山積みの状態ですが、健康に気をつけつつベストを尽くし、挑戦を続けていきます。

ちなみに今回の転職はLaprasさん経由でした。今週インタビューも掲載いただいておりますので、よろしければご覧ください:

note.lapras.com

あとド定番で恐縮ですがWe're hiringなので、戸田が選んだチームや社会的課題ってどんなのだ、というのが気になる方は採用情報も見ていただけると嬉しいです:

jobs.henry-app.jp

Gradleのjvm-test-suiteプラグインがテスト周りの定型コードを排除するのに便利そう

Gradle v7.5の時点ではまだIncubating段階の機能ではあるのですが、Gradleの新しいプラグイン jvm-test-suite がいい感じなので紹介します。

docs.gradle.org

解きたい課題:サブモジュールや統合テストが出てくるととたんに面倒になるビルドスクリプト

Gradleは設定をDSLで記述するので基本的には何でもありなのですが、やはり定形コード(boilerplate)は少ないほうがビルドスクリプトの見通しも良くなります。もちろんGradleは「設定より規約(Convention over Configuration)」の考えを持っているため、ある程度は空気を読んでSourceSetやTaskを自動的に生成してくれます。しかしテスト周りにおいてはこうした自動生成は十分ではなく、次に挙げるような課題がありました:

  1. サブプロジェクト全てに対して実行したタスクのレポートを統合するのが面倒。ここでレポートとは単体テスト実行結果、ないしJaCoCoによるカバレッジ測定結果などを指す。
  2. スモークテストや統合テストといった単体テスト以外のテストを実行する際に、SourceSetやConfiguration(依存管理)、Taskなどを自分で書かなくてはならない

例えばJaCoCoのカバレッジ測定結果をSonarQubeに渡すために、SpotBugsプロジェクトでは以下のようなタスクを自分で書いて実行しています。一部はプロジェクト固有の課題を解決するためのコードですが、複雑さが伝わればOKです:

task jacocoRootReport(type: JacocoReport) {
  description = 'Merge all coverage reports before submit to SonarQube'
  def reportTasks = project.getTasksByName("jacocoTestReport", true).minus(rootProject.jacocoTestReport)
  dependsOn reportTasks

  executionData.setFrom reportTasks.executionData
  sourceDirectories.setFrom reportTasks.sourceDirectories

  // Only enable class directories related to non-test project
  classDirectories.setFrom files(reportTasks.classDirectories).filter {
    !it.toString().contains("-test") && !it.toString().contains("Test") && !it.toString().contains("junit")
  }

  reports {
    // JaCoCo SonarQube plugin needs a XML file to parse
    // https://docs.sonarqube.org/display/PLUG/JaCoCo+Plugin
    xml.required = true
  }
}

jvm-test-suiteプラグインの新規性

jvm-test-suite プラグインは java-library など既存のプラグインを導入しているプロジェクトであれば既に有効化されています。既存のプラグイン達とのシームレスな統合を前提に開発されていると言っていいでしょう。

また別種のテスト追加やレポートの統合機能もこれに統合されており、例えば前述のJaCoCoレポート統合は test-report-aggregation プラグインによって提供されますが、 jvm-test-suite プラグインの提供するレールに乗っていれば testCodeCoverageReport というTaskが自動的に生成されます。先述の定形コードがまるっと削除できるわけです。

後述するSourceSetやConfiguraitonの自動生成と合わせて、多くの定形コードを排除するのに役立ちそうです。

jvm-test-suiteプラグインの提供するレール

jvm-test-suiteプラグインを適用する上で注意すべきはひとつ、テストの実行に必要な情報を testing Extensionに集約することです。

詳しい説明は公式サイトに譲りますが、例として spotbugs-gradle-plugin で試している設定は以下のようなものになりました:

testing {
    suites {
        val test by getting(JvmTestSuite::class) {
            useJUnitJupiter()
            dependencies {
                implementation(gradleTestKit()) // Gradle v7.6+ が必要。 https://github.com/gradle/gradle/issues/19849
            }
            targets {
                all {
                    testTask.configure {
                        maxParallelForks = Runtime.getRuntime().availableProcessors()
                    }
                }
            }
        }
        val functionalTest by registering(JvmTestSuite::class) {
            useSpock()
            testType.set(TestSuiteType.FUNCTIONAL_TEST)
            targets {
                all {
                    testTask.configure {
                        description = "Runs the functional tests."
                    }
                }
            }
        }
    }
}

これだけで functionalTest Taskと、その実行に必要なSourceSetとConfiguraitonが自動的に生成されます。useJUnitJupiter(), useJUnit(), useSpock(), useTestNG()など依存やバージョンを一括管理してくれる便利メソッドもある(Gradle 7.6からuseKotlinTest() も増えそう)ため、今後は testImplemenentation などのConfiguraitonを自分で設定する機会も激減しそうです。

まとめ

jvm-test-suite プラグインを利用することで、ちょっと複雑なGradleプロジェクトでは頻出だったテスト周りの定形コードの多くを省くことができます。 サンプルプロジェクトがGradle公式から提供されているので、興味のある方は確認してみてください!

github.com