Spring Boot プロジェクトの自動テストを CircleCI で始めるための2ステップ

この記事は Calendar for CircleCI Advent Calendar 2021 | Advent Calendar 2021 - Qiita の4日目の記事です。昨日は、おおはらぶちょーの 古いCircleCI Slack通知からの移行の道 でした。さすがぶちょー、ちゃんとしてるなぁ。相変わらずかっこいいです!

こんにちは

椎葉です。10月から CircleCI で Senior Full Stack Engineer として仕事をしてます。

フルリモートなので、まだ直接は誰とも合ったことがないんですけど、チームのみんなが優しかったり、オンラインの社内イベントが定期的に開催されてたり( CircleCIの週一回のチームチャレンジとは?! | CircleCI Japan )、世界中のみんなとチャットで話ができたりして、楽しく過ごしてます。その辺は、また気が向いたら書こうかな。

じつはこれまで CircleCI は雰囲気だけで使ってたので、細かいところとか全然知らなくて、今ドキュメントを読みながら「へー」とか「ほほー」とか言いつつ勉強してるところです。ということで、今日は入門者らしく Spring Boot プロジェクトの自動テストを CircleCI で始めてみようと思います!

準備 - プロジェクトを作る

とりあえず Spring Boot のプロジェクトが欲しいので Spring Initializr ( https://start.spring.io/ ) を開いて適当にこんな感じで用意

f:id:bufferings:20211202185002p:plain:w800

Java は17でいっか。なんとなく Spring Web を追加。最初からテストが1個入ってて ./mvnw test を叩くと実行できるから、これでいこう

❯ ./mvnw test
...
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 3.801 s - in com.example.demo.DemoApplicationTests
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  13.910 s
[INFO] Finished at: 2021-12-02T20:45:21+09:00
[INFO] ------------------------------------------------------------------------

これを GitHub に push しといた

GitHub - bufferings/springboot-cci-maven

じゃ、このプロジェクトを CircleCI で自動テストできるようにする。タイトルにも書いた通り2ステップ

ステップ1 - CircleCI の設定を追加

CircleCI の設定は .circleci/config.yml に書く。Maven の Orb を使ったらこれだけでいけそう

version: 2.1

orbs:
  maven: circleci/maven@1.2.0

workflows:
  maven_test:
    jobs:
      - maven/test:
          executor:
            name: "maven/default"
            tag: "17.0.0"

デフォルトだと Java 13 みたいなので 17 を指定してる。commit して push。

ステップ2 - CircleCI を有効にする

CircleCI に GitHub アカウントでログインして Projects から対象のリポジトリの Set Up Project をクリック

f:id:bufferings:20211202211018p:plain:w800

main ブランチに push しといたので、"main" って入力して Let's Go!

f:id:bufferings:20211203202803p:plain:w400

これで設定おわり。お疲れさまでした!

動作確認

設定が終わるとそのままパイプラインが実行される

テスト成功したーヽ(=´▽`=)ノ(実は設定ファイル何回か失敗した

f:id:bufferings:20211203203314p:plain:w800

これで GitHub に変更を push するたびにテストが実行される。main ブランチだけじゃなくてどんなブランチでも動く。便利。

GitHub のブランチの履歴のところでも出るし

f:id:bufferings:20211203210508p:plain:w500

プルリクエストを作ったらそこにも出てくる

f:id:bufferings:20211203210739p:plain:w500

プルリクエスト出したときにテストが失敗すると、こんな感じ

f:id:bufferings:20211203210959p:plain:w500

テストが失敗してたらマージできないように GitHub で設定しておくと、ぽいかな!やってみよう

GitHub の Settings の Branch のとこで ci/circleci: test を指定すると

f:id:bufferings:20211204000218p:plain:w800

テストがコケてたらマージできない(実際は管理者だからできるけど)

f:id:bufferings:20211204000453p:plain:w800

(๑•̀ㅂ•́)و✧

というわけで

Spring Boot プロジェクト(に限らず Maven プロジェクト。Gradle プロジェクトでも似たような感じだと思う)を GitHub に持ってる人(BitBucket でも同じ感じなのかな?)は、とても簡単なので、とりあえず自動テストを有効にするところから始めてみたら良いかなって思います!その後、ちょこちょこテストを足していけたら楽しそう。はい、以上ですー!

明日は @sogahisashi さんの「AWS Fargate への自動/手動デプロイ」です!楽しみー。

ここからはおまけ

そもそも↓が何をしてくれてるの?ってのが気になる

version: 2.1

orbs:
  maven: circleci/maven@1.2.0

workflows:
  maven_test:
    jobs:
      - maven/test:
          executor:
            name: "maven/default"
            tag: "17.0.0"

この maven/test の内容を知りたい。ので Orb を確認

CircleCI Developer Hub - circleci/maven

circleci/maven Orb の test ジョブを実行してるってことだからここだな

    test:
        description: |
            Checkout, build, test, and upload test results for a Maven project.
        executor: <<parameters.executor>>
        parameters:
            app_src_directory:
                default: ""
                description: Useful when the source of your maven project is nott in the root directory of your git repo. Supply the name of the directory or relative path of the directory containing your source code.
                type: string
            command:
                default: verify
                description: The maven command to run.
                type: string
            executor:
                default: default
                description: The name of custom executor to use
                type: executor
            maven_command:
                default: mvn
                description: Specify a custom path for invoking maven
                type: string
            settings_file:
                default: ""
                description: Specify a custom settings file to use (optional)
                type: string
            test_results_path:
                default: target/surefire-reports
                description: The path to the test results.
                type: string
        steps:
            - checkout
            - with_cache:
                app_src_directory: << parameters.app_src_directory >>
                maven_command: << parameters.maven_command >>
                settings_file: << parameters.settings_file >>
                steps:
                    - run:
                        command: |
                            if [ -n "<< parameters.settings_file >>" ]; then
                              set -- "$@" --settings "<< parameters.settings_file >>"
                            fi
                            << parameters.maven_command >> << parameters.command >> "$@"
                        name: Run Tests
                        working_directory: << parameters.app_src_directory >>
            - process_test_results:
                test_results_path: << parameters.test_results_path >>

パラメータの設定が色々あるけど、そこは心の目でなんとなく読めるのでいいとして、実際にやってることは

  1. ソースコードをチェックアウトして
  2. with_cache コマンドの中で mvn verify を実行して
  3. テスト結果を保存してる

ふむ。。。

with_cache 🤔

この with_cache は何をやってるんだろう?ってことなんだけど、パラメータ部分は飛ばして step のところだけ書くとこう:

steps:
  - run:
      command: find . -name 'pom.xml' | sort | xargs cat > /tmp/maven_cache_seed
      name: Generate Cache Checksum
      working_directory: $CIRCLE_WORKING_DIRECTORY/<< parameters.app_src_directory >>
  - restore_cache:
      key: 'maven-{{ checksum "/tmp/maven_cache_seed" }}'
  - run:
      command: |
        if [ -n "<< parameters.settings_file >>" ]; then
          set -- "$@" --settings "<< parameters.settings_file >>"
        fi
      name: Install Dependencies
      working_directory: << parameters.app_src_directory >>
  - when:
      condition: << parameters.verify_dependencies >>
      steps:
        - run:
            command: '<< parameters.maven_command >> dependency:go-offline "$@"'
            name: Verify dependencies
            working_directory: << parameters.app_src_directory >>
  - steps: << parameters.steps >>
  - save_cache:
      key: 'maven-{{ checksum "/tmp/maven_cache_seed" }}'
      paths:
        - ~/.m2/repository

pom.xmlチェックサムをキーにして、キャッシュを読み込み & 保存してる。キャッシュの対象は ~/.m2/repository

つまり、pom.xml が同じ間はキャッシュを使ってくれる。~/.m2/repository には依存関係の JAR ファイルがいっぱい入ってるから、キャッシュを使うと2回目以降のビルドが短い時間で済む。さっきのプロジェクトを確認してみると、こんなシンプルなプロジェクトでも 42s → 18s になってる。大きなプロジェクトだともっと便利ね

f:id:bufferings:20211204003251p:plain:w800

キャッシュの有効期限は15日間なことに注意しておきたい

https://circleci.com/docs/ja/2.0/caching/#cache-expiration

実行環境は?

何をやってるかは分かった。今度は、それをどんな環境で実行してるのかが気になる。ので executor をチェック。これだね

description: >
  The latest minor and patch update of the version 13 JDK image provided by
  CircleCI.
docker:
  - image: 'cimg/openjdk:<<parameters.tag>>'
parameters:
  tag:
    default: '13.0'
    description: >
      Can be changed to any of the available tags listed on the DockerHub for
      this image.

      https://hub.docker.com/r/cimg/openjdk/tags
    type: string

ということで、使ってるのは cimg/openjdk:13.0 のイメージってことだなー。なので、僕は tag: "17.0.0"Java 17 を使うようにしたのだった

ところで、どの JDK なの?

ってのがまぁ気になるので見てみる

❯ docker run --platform linux/x86_64 --rm -ti cimg/openjdk:17.0.0 java --version
openjdk 17 2021-09-14
OpenJDK Runtime Environment Temurin-17+35 (build 17+35)
OpenJDK 64-Bit Server VM Temurin-17+35 (build 17+35, mixed mode, sharing)

これ

ついでに、Maven と Gradle も見とくか

circleci@993619baf20e:~/project$ mvn --version
Apache Maven 3.8.2 (ea98e05a04480131370aa0c110b8c54cf726c06f)
Maven home: /opt/apache-maven
Java version: 17, vendor: Eclipse Adoptium, runtime: /usr/local/jdk-17.0.0
Default locale: en_US, platform encoding: UTF-8
OS name: "linux", version: "5.10.25-linuxkit", arch: "amd64", family: "unix"
circleci@993619baf20e:~/project$ gradle --version

------------------------------------------------------------
Gradle 7.2
------------------------------------------------------------

Build time:   2021-08-17 09:59:03 UTC
Revision:     a773786b58bb28710e3dc96c4d1a7063628952ad

Kotlin:       1.5.21
Groovy:       3.0.8
Ant:          Apache Ant(TM) version 1.10.9 compiled on September 27 2020
JVM:          17 (Eclipse Adoptium 17+35)
OS:           Linux 5.10.25-linuxkit amd64

なるほどー。気が済んだので、今日はこれくらいでー!楽しかった

次のステップ

簡単に自動テストを始められるとは言っても、まぁ、やっぱり細かく自分で色々やりたい気持ちなので、またアドベントカレンダーの空いてるところで続きやろうかなー!