Carpe Diem

備忘録

Bazelを使ってみる その6(テスト)

概要

Bazel解説第6弾です。

Bazelを使ってみる その1(Goのビルド) - Carpe Diem
Bazelを使ってみる その2(protobufのビルド) - Carpe Diem
Bazelを使ってみる その3(docker imageのビルド) - Carpe Diem
Bazelを使ってみる その4(gRPCのビルド) - Carpe Diem
Bazelを使ってみる その5(リモートキャッシュ) - Carpe Diem

今回はテストについて説明します。

環境

  • Bazel v4.2.2

各種テスト

Goにおけるテスト周りでは以下の考慮できてれば大丈夫でしょう。

  • unit test
    • raceフラグを付けたい場合
  • coverage
  • integration test

今回は以下のようなファイル構成の際において

├── dice
│   ├── dice.go
│   ├── dice_integration_test.go
│   └── dice_test.go
├── docker-compose.yml
├── go.mod
└── go.sum

それぞれのやり方を説明します。

unit test

BUILD.bazelの設定

Gazelleを使っている場合

$ bazel run //:gazelle

を実行すると、_test.goファイルが有ればそれをgo_testルールに含んでテスト対象としてくれます。

今回だと以下のdice/BUILD.bazelが生成されます。

load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")

go_library(
    name = "dice",
    srcs = ["dice.go"],
    importpath = "github.com/jun06t/bazel-sample/test/dice",
    visibility = ["//visibility:public"],
    deps = ["@com_github_go_redis_redis_v8//:go_default_library"],
)

go_test(
    name = "dice_test",
    srcs = ["dice_test.go"],
    embed = [":dice"],
    deps = ["@com_github_stretchr_testify//assert:go_default_library"],
)

実行方法

$ bazel test //...

とすれば対象のテストをすべて実行してくれます。

raceフラグを付けたい場合

raceフラグを付けたい時は、先程のコマンドに@io_bazel_rules_go//go/config:raceオプションを付けます。

$ bazel test --@io_bazel_rules_go//go/config:race //...

デフォルトでONにしたい場合は.bazelrcで以下を追記すると良いでしょう。

test --@io_bazel_rules_go//go/config:race

coverage

カバレッジを出力したい場合はbazelでコマンドが用意されています。

$ bazel coverage //...

これでcoverage.datという名前のカバレッジレポートを生成できます。

ただ出力されたレポートはサンドボックスにある&パッケージ毎にバラバラになっています。
なので以下のようにコマンドでまとめる処理を入れると良いでしょう。

echo "mode: set" > coverage.out
find bazel-out/ -name "coverage.dat" | xargs tail -q -n +2 >> coverage.out

レポートの確認

$ go tool cover -html=coverage.out

問題なく表示できています。

ビルドタグを用いたintegration test

BUILD.bazelの対応

Goでintegration testを行う際、ビルドタグを用いてunit testとは分ける手法があります。

mickey.dev

通常ビルドタグが付いたテストはgazelleでビルドしてもgo_testの対象からは含まれなくなります。

対象にしたい場合は

$ bazel run //:gazelle -- -build_tags=integration

とすると、先程のdice/BUILD.bazelにintegration testも含めるようにしてくれます。

load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")

go_library(
    name = "dice",
    srcs = ["dice.go"],
    importpath = "github.com/jun06t/bazel-sample/test/dice",
    visibility = ["//visibility:public"],
    deps = ["@com_github_go_redis_redis_v8//:redis"],
)

go_test(
    name = "dice_test",
    srcs = [
        "dice_integration_test.go",
        "dice_test.go",
    ],
    embed = [":dice"],
    deps = [
        "@com_github_stretchr_testify//assert",
        "@com_github_stretchr_testify//require",
    ],
)

実行方法

go test -tags integrationのようにタグ付きで実行したい時はio_bazel_rules_go//go/config:tagsオプションを付けます。

$ bazel test --@io_bazel_rules_go//go/config:tags=integration //...

このようなビルドオプションは rules_go/modes.rst at master · bazelbuild/rules_go · GitHub にまとまっています。

ローカルのファイルを用いたテスト

fixturesのようなテスト用のデータファイルを使いたい場合です。

同じディレクト

以下のディレクトリ構造でdice_test.gotest1.yamlに依存しているとします。

.
├── BUILD.bazel
├── WORKSPACE
├── deps.bzl
├── dice
│   ├── BUILD.bazel # ここを編集
│   ├── dice.go
│   ├── dice_integration_test.go
│   ├── dice_test.go
│   └── test1.yaml # 対象のデータファイル
├── go.mod
└── go.sum

この場合は以下のようにdata属性を使って依存関係を解決できます。

go_test(
    name = "dice_test",
    srcs = [
        "dice_integration_test.go",
        "dice_test.go",
    ],
    data = [
        "test1.yaml",
    ],
    embed = [":dice"],
    deps = [
        "@com_github_stretchr_testify//assert",
        "@com_github_stretchr_testify//require",
    ],
)

サブディレクト

サブディレクトリにファイルが存在する場合はglobでディレクトリ全体を指定すると良いです。

.
├── BUILD.bazel
├── WORKSPACE
├── deps.bzl
├── dice
│   ├── BUILD.bazel # ここを編集
│   ├── dice.go
│   ├── dice_integration_test.go
│   ├── dice_test.go
│   └── testdata
│       └── test2.yaml # 対象のデータファイル
├── go.mod
└── go.sum
go_test(
    name = "dice_test",
    srcs = [
        "dice_integration_test.go",
        "dice_test.go",
    ],
    data = glob(["testdata/**"]),
    embed = [":dice"],
    deps = [
        "@com_github_stretchr_testify//assert",
        "@com_github_stretchr_testify//require",
    ],
)

複数箇所に存在する場合

以下のように複数の場所に存在する場合はfilegroup()を使ってターゲットを定義して指定するとできます。

.
├── BUILD.bazel
├── WORKSPACE
├── deps.bzl
├── dice
│   ├── BUILD.bazel # ここを編集
│   ├── dice.go
│   ├── dice_integration_test.go
│   ├── dice_test.go
│   ├── test1.yaml # 対象のデータファイル
│   └── testdata
│       └── test2.yaml # 対象のデータファイル
├── go.mod
└── go.sum
go_test(
    name = "dice_test",
    srcs = [
        "dice_integration_test.go",
        "dice_test.go",
    ],
    data = [
        "test1.yaml",
        ":test_dir",
    ],
    embed = [":dice"],
    deps = [
        "@com_github_stretchr_testify//assert",
        "@com_github_stretchr_testify//require",
    ],
)

filegroup(
    name = "test_dir",
    srcs = glob(["testdata/**"]),
)

その他

テストで役立つオプション

あらかじめ.bazelrcに以下を追記しておくと、エラーの表示などが見やすくなります。

build --show_timestamps --verbose_failures
test --test_output=errors --test_verbose_timeout_warnings

各オプションを説明すると以下です。

オプション 説明
show_timestamps 各アクションのタイムスタンプをコンソールで表示する
verbose_failures 失敗した際にどんなコマンドだったか表示する
test_output summary, errors, all, streamedのどれかを設定する。デフォルトはstreamed
errorsにすると失敗時にログを表示してくれる
test_verbose_timeout_warnings 実際のテストの実行時間がテストによって定義されたタイムアウトと一致しない場合に追加の警告を表示する

runフラグはなさそう

Goに対応したビルドオプションであったり、Bazel, GazelleのIssueをざっと見てみましたが、-runにあたるオプションは見当たりませんでした。

Bazelを使う際の方針にも関係しますが「無理にすべてをBazelに任せる必要はなく、CIなど再現性が重要視されるされるケースで使えば良い」というくらいの緩い方針で使うのがちょうど良いでしょう。 なので-runなど一部だけテストしたい時は通常通りのgo test -run=xxxで良いと思います。

サンプルコード

今回のコードはこちら。

github.com

まとめ

Bazelを使った場合のGoでのテスト周りについてまとめました。

参考