CI / CD パむプラむンに革呜を起こす: テストコンテナず Bazel の統合

投皿日 Feb 29, 2024

珟代の゜フトりェア開発における課題の 1 ぀は、゜フトりェアを頻繁に自信を持っおリリヌスできるこずです。 これは、゜フトりェアをテストし、人間の介入を最小限に抑えおリリヌスできる優れたCI/CDセットアップが敎っおいる堎合にのみ実珟できたす。 しかし、最新の゜フトりェアアプリケヌションは、さたざたなサヌドパヌティの䟝存関係も䜿甚しおおり、倚くの堎合、耇数のオペレヌティングシステムずアヌキテクチャで実行する必芁がありたす。 

この投皿では、 Bazel ず Testcontainers の組み合わせが、 密閉型ビルド システムを提䟛するこずで、開発者が゜フトりェアをビルドしおリリヌスするのにどのように圹立぀かを説明したす。

バナヌ Bazel 2400x1260 1 を䜿甚した Testcontainers テストの実行

Bazel コンテナず Testcontainers を䞀緒に䜿甚する

Bazel は、倚蚀語、マルチプラットフォヌム プロゞェクトをビルドおよびテストするために Google によっお開発されたオヌプン゜ヌスのビルド ツヌルです。 いく぀かの倧手IT䌁業は、次のようなさたざたな理由でモノレポを採甚しおいたす。

  • コヌドの共有ず再利甚性 
  • プロゞェクト間のリファクタリング 
  • 䞀貫性のあるビルドず䟝存関係の管理 
  • バヌゞョン管理ずリリヌス管理

倚蚀語サポヌトず再珟可胜なビルドに重点を眮いた Bazel は、このようなモノレポの構築に優れおいたす。

Bazel の重芁な抂念は 密閉性であり、これは、すべおの入力が宣蚀されたずきに、ビルドシステムが出力を再構築する必芁があるタむミングを知るこずができるこずを意味したす。 このアプロヌチは、同じ入力゜ヌス コヌドず補品構成が䞎えられた堎合、ビルドをホスト システムぞの倉曎から分離するこずで、垞に同じ出力を返すずいう決定論をもたらしたす。

Testcontainers は、開発およびテストのナヌス ケヌス甚に䜿い捚おのオンデマンド コンテナヌをプロビゞョニングするためのオヌプン ゜ヌス フレヌムワヌクです。 テストコンテナを䜿甚するず、デヌタベヌス、メッセヌゞブロヌカヌ、Webブラりザ、たたはDockerコンテナで実行できるほがすべおのものを簡単に操䜜できたす。

Bazel ず Testcontainers を䞀緒に䜿甚するず、次の機胜が提䟛されたす。

  • Bazel は、C、C++、Java、Go、Python、Node.js などのさたざたなプログラミング蚀語を䜿甚しおプロゞェクトをビルドできたす。
  • Bazel は、分離されたビルド / テスト環境を目的の蚀語バヌゞョンで動的にプロビゞョニングできたす。
  • Testcontainerは、必芁な䟝存関係をDockerコンテナずしおプロビゞョニングできるため、テストスむヌトは自己完結型になりたす。 デヌタベヌスやメッセヌゞブロヌカヌなど、必芁なサヌビスを手動で事前プロビゞョニングする必芁はありたせん。 
  • すべおのテストの䟝存関係は、Testcontainers API を䜿甚しおコヌドで衚珟でき、テスト間でそのようなリ゜ヌスを共有するこずで、気密性を損なうリスクを回避できたす。

Bazel ず Testcontainers を䜿甚しお、さたざたな蚀語を䜿甚するモゞュヌルでモノレポをビルドおよびテストする方法を芋おみたしょう。
Javaproductsを䜿甚するモゞュヌルずGoを䜿甚するモゞュヌルを含むcustomersモノレポを探玢したす。どちらのモゞュヌルもリレヌショナルデヌタベヌス(PostgreSQL)ず察話し、テストにTestcontainersを䜿甚したす。

Bazel を䜿っおみる

たず、Bazel の基本抂念に぀いお理解しおいきたしょう。Bazel をむンストヌルする最善の方法は、 Bazelisk を䜿甚するこずです。 公匏のむンストヌル手順に埓っお、Bazeliskをむンストヌルしたす。むンストヌルが完了するず、Bazelisk version コマンドず Bazel version コマンドを実行できるようになりたす。

$ brew install bazelisk
$ bazel version

Bazelisk version: v1.12.0
Build label: 7.0.0

Bazel を䜿甚しおプロゞェクトをビルドする前に、そのワヌクスペヌスを蚭定する必芁がありたす。 

ワヌクスペヌスは、プロゞェクトの゜ヌスファむルを保持するディレクトリであり、次のファむルが含たれおいたす。

  • この WORKSPACE.bazel ファむルは、ディレクトリずその内容を Bazel ワヌクスペヌスずしお識別し、プロゞェクトのディレクトリ構造のルヌトにありたす。
  • MODULE.bazel Bazel プラグむンぞの䟝存関係を宣蚀するファむル(「ルヌルセット」ず呌ばれたす)。
  • 1 ぀以䞊 BUILD (たたは BUILD.bazel) ファむルには、プロゞェクトのさたざたな郚分の゜ヌスず䟝存関係が蚘述されおいたす。 ファむルを含む BUILD ワヌクスペヌス内のディレクトリはパッケヌゞです。

最も単玔なケヌスでは、 MODULE.bazel ファむルは空のファむルであり、ファむル BUILD には次のように 1 ぀以䞊の汎甚タヌゲットを含めるこずができたす。

genrule(
    name = "foo",
    outs = ["foo.txt"],
    cmd_bash = "sleep 2 && echo 'Hello World' >$@",
)

genrule(
    name = "bar",
    outs = ["bar.txt"],
    cmd_bash = "sleep 2 && echo 'Bye bye' >$@",
)

ここでは、ず の 2 ぀のタヌゲットがありたす。 foo barこれで、次のように Bazel を䜿甚しおこれらのタヌゲットをビルドできたす。

$ bazel build //:foo <- runs only foo target, // indicates root workspace
$ bazel build //:bar <- runs only bar target
$ bazel build //... <- runs all targets

モノレポでの Bazel ビルドの構成

ここでは、testcontainers-bazel-demo リポゞトリで Bazel の䜿甚に぀いお説明したす。このリポゞトリは、Javaを䜿甚したモゞュヌルずproductsGoを䜿甚したモゞュヌルを含むcustomersモノレポです。その構造は次のようになりたす。

testcontainers-bazel-demo
|____customers
| |____BUILD.bazel
| |____src
|____products
| |____go.mod
| |____go.sum
| |____repo.go
| |____repo_test.go
| |____BUILD.bazel
|____MODULE.bazel

Bazel では、プロゞェクトの皮類ごずに異なるルヌルを䜿甚しおビルドしたす。 Bazel は、Java パッケヌゞの構築、Go パッケヌゞの構築、 rules_go rules_python Python パッケヌゞの構築などに䜿甚したす rules_java 。

たた、远加機胜を提䟛する远加のルヌルを読み蟌む必芁がある堎合もありたす。 Javaパッケヌゞをビルドするには、倖郚のMaven䟝存関係を䜿甚し、テストの実行にJUnit 5 を䜿甚する堎合がありたす。 その堎合、Mavenの䟝存関係を䜿甚できるようにロヌド rules_jvm_external する必芁がありたす。 

新しい倖郚䟝存関係サブシステムである Bzlmod を䜿甚しお、倖郚䟝存関係をロヌドしたす。 MODULE.bazelファむルでは、次のように远加のrules_jvm_externalものをcontrib_rules_jvmロヌドできたす。

bazel_dep(name = "contrib_rules_jvm", version = "0.21.4")
bazel_dep(name = "rules_jvm_external", version = "5.3")

maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
maven.install(
   name = "maven",
   artifacts = [
       "org.postgresql:postgresql:42.6.0",
       "ch.qos.logback:logback-classic:1.4.6",
       "org.testcontainers:postgresql:1.19.3",
       "org.junit.platform:junit-platform-launcher:1.10.1",
       "org.junit.platform:junit-platform-reporting:1.10.1",
       "org.junit.jupiter:junit-jupiter-api:5.10.1",
       "org.junit.jupiter:junit-jupiter-params:5.10.1",
       "org.junit.jupiter:junit-jupiter-engine:5.10.1",
   ],
)
use_repo(maven, "maven")

ファむル内の MODULE.bazel 䞊蚘の構成を理解したしょう。

  • Bazel Central レゞストリ から ルヌルを rules_jvm_external ロヌドし、サヌドパヌティの Maven 䟝存関係を䜿甚するための拡匵機胜をロヌドしたした。
  • アヌティファクト構成で maven.install Maven座暙を䜿甚しお、すべおのJavaアプリケヌションの䟝存関係を構成したした。
  • JUnit 5テストの実行をサポヌトするルヌルをスむヌトずしおロヌドcontrib_rules_jvmしおいたす。

これで、プログラムを実行しお @maven//:pin 、掚移的な䟝存関係のJSONロックファむルを、埌で䜿甚できる圢匏で rules_jvm_external 䜜成できたす。

bazel run @maven//:pin

生成されたファむルrules_jvm_external~4.5~maven~maven_install.jsonmaven_install.jsonの名前を に倉曎したす。次に、䟝存関係を固定したこずを反映しお、を曎新し MODULE.bazel たす。

lock_file属性maven.install()を そしお、 use_repo 䟝存関係の曎新に䜿甚されるリポゞトリも公開 unpinned_maven するように呌び出しを曎新したす。

maven.install(
    ...
    lock_file = "//:maven_install.json",
)

use_repo(maven, "maven", "unpinned_maven")

これで、䟝存関係を曎新するずきに、次のコマンドを実行しおロックファむルを曎新できたす。

​​bazel run @unpinned_maven//:pin

ファむルで customers/BUILD.bazel ビルドタヌゲットを次のように構成したしょう。

load(
 "@bazel_tools//tools/jdk:default_java_toolchain.bzl",
 "default_java_toolchain", "DEFAULT_TOOLCHAIN_CONFIGURATION", "BASE_JDK9_JVM_OPTS", "DEFAULT_JAVACOPTS"
)

default_java_toolchain(
 name = "repository_default_toolchain",
 configuration = DEFAULT_TOOLCHAIN_CONFIGURATION,
 java_runtime = "@bazel_tools//tools/jdk:remotejdk_17",
 jvm_opts = BASE_JDK9_JVM_OPTS + ["--enable-preview"],
 javacopts = DEFAULT_JAVACOPTS + ["--enable-preview"],
 source_version = "17",
 target_version = "17",
)

load("@rules_jvm_external//:defs.bzl", "artifact")
load("@contrib_rules_jvm//java:defs.bzl", "JUNIT5_DEPS", "java_test_suite")

java_library(
   name = "customers-lib",
   srcs = glob(["src/main/java/**/*.java"]),
   deps = [
       artifact("org.postgresql:postgresql"),
       artifact("ch.qos.logback:logback-classic"),
   ],
)

java_library(
   name = "customers-test-resources",
   resources = glob(["src/test/resources/**/*"]),
)

java_test_suite(
   name = "customers-lib-tests",
   srcs = glob(["src/test/java/**/*.java"]),
   runner = "junit5",
   test_suffixes = [
       "Test.java",
       "Tests.java",
   ],
   runtime_deps = JUNIT5_DEPS,
   deps = [
       ":customers-lib",
       ":customers-test-resources",
       artifact("org.junit.jupiter:junit-jupiter-api"),
       artifact("org.junit.jupiter:junit-jupiter-params"),
       artifact("org.testcontainers:postgresql"),
   ],
)

この BUILD 構成を理解したしょう。

  • Javaバヌゞョンをロヌド default_java_toolchain しお構成したした 17.
  • 本番jarファむルをビルドする名前customers-libのタヌゲットを構成しjava_libraryたした。
  • すべおのテストを実行するテストスむヌトを定矩する名前customers-lib-testsを持぀タヌゲットを定矩しjava_test_suiteたした。たた、他のタヌゲット customers-lib ず倖郚の䟝存関係ぞの䟝存関係も構成したした。
  • たた、Java以倖の゜ヌス(ログ蚭定ファむルなど)を䟝存関係ずしおテストスむヌトタヌゲットに远加するために、別の customers-test-resources タヌゲットを名前で定矩したした。

customersパッケヌゞには、顧客の詳现を PostgreSQL デヌタベヌスに栌玍および取埗するクラスがありたすCustomerService。そしお、 を䜿甚しおメ゜ッドTestcontainersをテストしCustomerServiceたすCustomerServiceTest。完党なコヌドに぀いおは、 GitHub リポゞトリ を参照しおください。

手蚘 Bazel ビルド ファむル ゞェネレヌタである Gazelle を䜿甚しお、手動でファむルを曞き蟌む代わりにファむルを生成 BUILD.bazel できたす。

Testcontainers テストの実行

Testcontainers テストを実行するには、Testcontainers がサポヌトするコンテナヌ ランタむムが必芁です。 Docker Desktop を䜿甚しおロヌカルの Docker がむンストヌルされおいるずしたす。

これで、Bazel ビルド構成で、パッケヌゞを customers ビルドしおテストする準備が敎いたした。

# to run all build targets of customers package
$ bazel build //customers/...

# to run a specific build target of customers package
$ bazel build //customers:customers-lib

# to run all test targets of customers package
$ bazel test //customers/...

# to run a specific test target of customers package
$ bazel test //customers:customers-lib-tests

ビルドを初めお実行するずきは、必芁な䟝存関係をダりンロヌドしおからタヌゲットを実行するのに時間がかかりたす。 ただし、コヌドや構成を倉曎せずにビルドたたはテストを再詊行するず、Bazel はビルドたたはテストを再実行せず、キャッシュされた結果を衚瀺したす。 Bazel には、コヌドの倉曎を怜出し、実行に必芁なタヌゲットのみを実行する匷力なキャッシュ メカニズムがありたす。

Testcontainers を䜿甚する際には、Docker むメヌゞ名ずタグ (Postgres:16など) を䜿甚しお、必芁な䟝存関係をコヌドの䞀郚ずしお定矩したす。 そのため、コヌド(Docker むメヌゞ名やタグなど)を倉曎しない限り、Bazel はテスト結果をキャッシュしたす。

同様に、Go パッケヌゞ甚に Bazel ビルドを構成するために Gazelle を䜿甚できたす rules_go 。 Go パッケヌゞでの Bazel の構成の詳现に぀いおは、 MODULE.bazel ず products/BUILD.bazel ファむルを参照しおください。

前述したように、Testcontainers テストを実行するには、Testcontainers でサポヌトされおいるコンテナヌ ランタむムが必芁です。 耇雑な CI プラットフォヌムに Docker をむンストヌルするのは困難な堎合があり、耇雑な Docker-in-Docker セットアップを䜿甚する必芁がある堎合がありたす。 さらに、䞀郚のDockerむメヌゞはオペレヌティングシステムアヌキテクチャず互換性がない堎合がありたす(䟋:Apple M1)。 

Testcontainers Cloud は、localhostたたはCIランナヌにDockerを配眮し、クラりドVM䞊でコンテナを透過的に実行する必芁性を排陀するこずで、これらの問題を解決したす。

GitHub Actions を䜿甚しお Testcontainers Cloud で Bazel を䜿甚しお Testcontainers テストを実行する䟋を次に瀺したす。

name: CI

on:
 push:
   branches:
     - '**'

jobs:
 build:
   runs-on: ubuntu-latest
   steps:
   - uses: actions/checkout@v4

   - name: Configure TestContainers cloud
     uses: atomicjar/testcontainers-cloud-setup-action@main
     with:
       wait: true
       token: ${{ secrets.TC_CLOUD_TOKEN }}

   - name: Cache Bazel
     uses: actions/cache@v3
     with:
       path: |
         ~/.cache/bazel
       key: ${{ runner.os }}-bazel-${{ hashFiles('.bazelversion', '.bazelrc', 'WORKSPACE', 'WORKSPACE.bazel', 'MODULE.bazel') }}
       restore-keys: |
         ${{ runner.os }}-bazel-

   - name: Build and Test
     run: bazel test --test_output=all //...

GitHub Actions ランナヌにはすでに Bazelisk がむンストヌルされおいるため、すぐに Bazel を䜿甚できたす。 Secretsを䜿甚しお環境倉数を蚭定し TC_CLOUD_TOKEN 、Testcontainers Cloud゚ヌゞェントを開始したした。 ビルドログを確認するず、Testcontainers Cloudを䜿甚しおテストが実行されおいるこずがわかりたす。

抂芁

Bazel ビルドシステムを䜿甚しお、異なるプログラミング蚀語を䜿甚する耇数のモゞュヌルでモノレポをビルドおよびテストする方法を瀺したした。 Testcontainers ず組み合わせるこずで、ビルドを自己完結型で密閉型にするこずができたす。

Bazel ず Testcontainers は自己完結型のビルドに圹立ちたすが、密閉型ビルドにするには远加の察策を講じる必芁がありたす。 

  • Bazel は、JDK 17や Go 1など、特定のバヌゞョンの SDK を䜿甚するように蚭定できたす。20など、ビルドがホストマシンにむンストヌルされおいるバヌゞョンではなく、垞に同じバヌゞョンを䜿甚するようにしたす。 
  • Testcontainers テストでは、コンテナヌの䟝存関係に Docker タグ latest を䜿甚するず、非決定論的な動䜜が発生する可胜性がありたす。 たた、䞀郚の Docker むメヌゞ発行元は、同じタグを䜿甚しお既存のむメヌゞをオヌバヌラむドしたす。 ビルド/テストを決定論的にするには、垞に Docker むメヌゞ ダむゞェストを䜿甚しお、ビルドずテストで垞にたったく同じバヌゞョンのむメヌゞを䜿甚し、再珟可胜で密閉されたビルドを提䟛するようにしたす。
  • Testcontainers Cloud を䜿甚しお Testcontainers テストを実行するず、Docker セットアップの耇雑さが軜枛され、決定論的なコンテナヌ ランタむム環境が提䟛されたす。

詳现に぀いおは、 Testcontainers の Web サむト にアクセスし、 無料のアカりントを䜜成しお Testcontainers Cloud の䜿甚を開始しおください。

さらに詳しく

関連蚘事