Bazel で Starlark のコードをテストするには、いくつかの方法があります。この では、現在のベスト プラクティスとフレームワークをユースケース別に紹介しています。
ルールのテスト
Skylib には、
unittest.bzl
ルールのアクションやルールなど、ルールの分析時の動作を
接続できますこのようなテストは「分析テスト」と呼ばれ、現在最もパフォーマンスの高い
を使用してルールの内部動作をテストできます。
注意点:
テスト アサーションは、個別のテストランナー プロセスではなく、ビルド内で行われます。 テストによって作成されたターゲットには、 他のテストやビルドのターゲットと競合する場合があります。エラーが 発生した場合、Bazel では、エラーではなくビルドの破損と 発生します。
テスト対象のルールをセットアップし、 テスト アサーションを含むルールを作成します。このボイラープレートは 見てみましょう。マクロは注意点として、 読み込みフェーズで評価とターゲットが生成され、 実装関数は、後の分析フェーズまで実行されません。
分析テストは、かなり小さく軽量にすることを意図しています。確実 分析テストフレームワークの機能は、データの検証、 推移的依存関係の最大数(現在は 500)を持つターゲット。 これは、これらの機能を大規模なリソースで使用する場合、パフォーマンスに テストです。
基本原則は、Terraform の構成に依存するテストルールを 使用します。これにより、テスト対象ルールに、テスト対象のルールの 接続できます
テストルールの実装関数は、アサーションを実行します。もし
失敗した場合、fail()
を呼び出してもすぐには発生しません(
分析時のビルドエラーをトリガーします)。ただし、エラーを
生成されたスクリプトがテスト実行時に失敗します。
以下に、単純なおもちゃの例と、その後にアクションを確認する例を示します。
最も簡単な例
//mypkg/myrules.bzl
:
MyInfo = provider(fields = {
"val": "string value",
"out": "output File",
})
def _myrule_impl(ctx):
"""Rule that just generates a file and returns a provider."""
out = ctx.actions.declare_file(ctx.label.name + ".out")
ctx.actions.write(out, "abc")
return [MyInfo(val="some value", out=out)]
myrule = rule(
implementation = _myrule_impl,
)
//mypkg/myrules_test.bzl
:
load("@bazel_skylib//lib:unittest.bzl", "asserts", "analysistest")
load(":myrules.bzl", "myrule", "MyInfo")
# ==== Check the provider contents ====
def _provider_contents_test_impl(ctx):
env = analysistest.begin(ctx)
target_under_test = analysistest.target_under_test(env)
# If preferred, could pass these values as "expected" and "actual" keyword
# arguments.
asserts.equals(env, "some value", target_under_test[MyInfo].val)
# If you forget to return end(), you will get an error about an analysis
# test needing to return an instance of AnalysisTestResultInfo.
return analysistest.end(env)
# Create the testing rule to wrap the test logic. This must be bound to a global
# variable, not called in a macro's body, since macros get evaluated at loading
# time but the rule gets evaluated later, at analysis time. Since this is a test
# rule, its name must end with "_test".
provider_contents_test = analysistest.make(_provider_contents_test_impl)
# Macro to setup the test.
def _test_provider_contents():
# Rule under test. Be sure to tag 'manual', as this target should not be
# built using `:all` except as a dependency of the test.
myrule(name = "provider_contents_subject", tags = ["manual"])
# Testing rule.
provider_contents_test(name = "provider_contents_test",
target_under_test = ":provider_contents_subject")
# Note the target_under_test attribute is how the test rule depends on
# the real rule target.
# Entry point from the BUILD file; macro for running each test case's macro and
# declaring a test suite that wraps them together.
def myrules_test_suite(name):
# Call all test functions and wrap their targets in a suite.
_test_provider_contents()
# ...
native.test_suite(
name = name,
tests = [
":provider_contents_test",
# ...
],
)
//mypkg/BUILD
:
load(":myrules.bzl", "myrule")
load(":myrules_test.bzl", "myrules_test_suite")
# Production use of the rule.
myrule(
name = "mytarget",
)
# Call a macro that defines targets that perform the tests at analysis time,
# and that can be executed with "bazel test" to return the result.
myrules_test_suite(name = "myrules_test")
テストは bazel test //mypkg:myrules_test
で実行できます。
最初の load()
ステートメントとは別に、
ファイル:
テスト自体。それぞれが 1)分析時間で構成されます。 テストルールの実装関数、2)テストルールの宣言
analysistest.make()
によるテストルール、3)読み込み時間関数 (マクロ)を使用してテスト対象ルール(およびその依存関係)を宣言し、 適用できます。テストケース間でアサーションが変わらない場合は、1)と 2)の テストケースで共有する必要がありますテストスイート関数。各関数の読み込み時間関数を呼び出します。 すべてのテストをバンドルする
test_suite
ターゲットを宣言します。
一貫性を保つため、推奨される命名規則(「foo
」を略します)に従います。
テストでチェックする対象を説明するテスト名の部分
(上記の例では provider_contents
)。たとえば、JUnit テストメソッドが、
testFoo
という名前になります。
以下の手順を行います。
テストとテスト対象を生成するマクロは、 名前:
_test_foo
(_test_provider_contents
)テストルールのタイプは
foo_test
(provider_contents_test
)という名前にする必要があります。このルールタイプのターゲットのラベルは
foo_test
にする必要があります。 (provider_contents_test
)テストルールの実装関数には
_foo_test_impl
(_provider_contents_test_impl
)テスト対象のルールのターゲットのラベルとその依存関係 先頭に
foo_
(provider_contents_
)を付ける必要があります
すべてのターゲットのラベルは、同じグループ内の他のラベルと BUILD パッケージを使用します。テストには一意の名前を使用すると便利です。
障害テスト
特定の入力または特定の条件下でルールが失敗することを検証しておくと あります。これは、分析テスト フレームワークを使用して行うことができます。
analysistest.make
で作成したテストルールでは、expect_failure
を指定する必要があります。
failure_testing_test = analysistest.make(
_failure_testing_test_impl,
expect_failure = True,
)
テストルールの実装では、エラーの性質に関するアサーションを行う必要があります。 発生したエラー メッセージの例:
def _failure_testing_test_impl(ctx):
env = analysistest.begin(ctx)
asserts.expect_failure(env, "This rule should never work")
return analysistest.end(env)
また、テスト対象のターゲットに「manual」というタグを付けてください。
これを指定しないと、:all
を使用してパッケージ内のすべてのターゲットをビルドすると、
すると、ビルドエラーが表示されます。あり
「手動」に設定すると、テスト対象のターゲットは、明示的に指定された場合、または
手動以外のターゲット(テストルールなど)の依存関係:
def _test_failure():
myrule(name = "this_should_fail", tags = ["manual"])
failure_testing_test(name = "failure_testing_test",
target_under_test = ":this_should_fail")
# Then call _test_failure() in the macro which generates the test suite and add
# ":failure_testing_test" to the suite's test targets.
登録済みのアクションの確認
実行するアクションについてアサーションを行うテストを
(たとえば、ctx.actions.run()
を使用します)。この設定は
実装できます。例:
def _inspect_actions_test_impl(ctx):
env = analysistest.begin(ctx)
target_under_test = analysistest.target_under_test(env)
actions = analysistest.target_actions(env)
asserts.equals(env, 1, len(actions))
action_output = actions[0].outputs.to_list()[0]
asserts.equals(
env, target_under_test.label.name + ".out", action_output.basename)
return analysistest.end(env)
なお、analysistest.target_actions(env)
は
Action
オブジェクト:
テスト対象になります。
さまざまなフラグでルールの動作を確認する
特定のビルドで実際のルールが特定の動作をすることを確認します。 使用できます。たとえば、ユーザーが以下を指定すると、ルールの動作が変わります。
bazel build //mypkg:real_target -c opt
対
bazel build //mypkg:real_target -c dbg
一見すると、 ビルドフラグを指定します。
bazel test //mypkg:myrules_test -c opt
しかし、そうするとテストスイートに
-c opt
でルールの動作を検証するテストと、
-c dbg
でルールの動作を検証します。どちらのテストも実行できない
使用できます。
この問題は、テストを定義するときに必要なビルドフラグを指定することで解決できます。 ルール:
myrule_c_opt_test = analysistest.make(
_myrule_c_opt_test_impl,
config_settings = {
"//command_line_option:compilation_mode": "opt",
},
)
通常、テスト対象のターゲットは、現在のビルドフラグを指定して分析されます。
config_settings
を指定すると、指定したコマンドラインの値がオーバーライドされます。
。(指定されていないオプションについては、実際の
使用できます。
指定された config_settings
辞書では、コマンドライン フラグは
接頭辞に特別なプレースホルダ値 //command_line_option:
が付きます。
ご覧ください。
アーティファクトの検証
生成されたファイルが正しいことを確認するための主な方法は次のとおりです。
シェル、Python、その他の言語でテスト スクリプトを記述できます。 適切な
*_test
ルールタイプのターゲットを作成します。実施するテストの種類に応じた特別なルールを使用できます。
テスト ターゲットの使用
アーティファクトを検証する最も簡単な方法は、スクリプトと
BUILD ファイルに *_test
ターゲットを追加します。追加するアーティファクトは、
ターゲットのデータ依存関係である必要があります。検証ロジックが
複数のテストで再利用できるよう、コマンドライン ツールの
テスト ターゲットの args
属性で制御される引数。こちらが
上記の myrule
の出力が "abc"
であることを検証する例です。
//mypkg/myrule_validator.sh
:
if [ "$(cat $1)" = "abc" ]; then
echo "Passed"
exit 0
else
echo "Failed"
exit 1
fi
//mypkg/BUILD
:
...
myrule(
name = "mytarget",
)
...
# Needed for each target whose artifacts are to be checked.
sh_test(
name = "validate_mytarget",
srcs = [":myrule_validator.sh"],
args = ["$(location :mytarget.out)"],
data = [":mytarget.out"],
)
カスタムルールの使用
より複雑な方法として、シェル スクリプトをテンプレートとして記述し、 新しいルールによってインスタンス化されます。これには、より多くの間接性と Starlark が含まれます。 BUILD ファイルがすっきりします。副次的なメリットとして、 スクリプトの代わりに Starlark で前処理を行うことができ、スクリプトは シンボリックなプレースホルダ( 使用することをおすすめします。
//mypkg/myrule_validator.sh.template
:
if [ "$(cat %TARGET%)" = "abc" ]; then
echo "Passed"
exit 0
else
echo "Failed"
exit 1
fi
//mypkg/myrule_validation.bzl
:
def _myrule_validation_test_impl(ctx):
"""Rule for instantiating myrule_validator.sh.template for a given target."""
exe = ctx.outputs.executable
target = ctx.file.target
ctx.actions.expand_template(output = exe,
template = ctx.file._script,
is_executable = True,
substitutions = {
"%TARGET%": target.short_path,
})
# This is needed to make sure the output file of myrule is visible to the
# resulting instantiated script.
return [DefaultInfo(runfiles=ctx.runfiles(files=[target]))]
myrule_validation_test = rule(
implementation = _myrule_validation_test_impl,
attrs = {"target": attr.label(allow_single_file=True),
# You need an implicit dependency in order to access the template.
# A target could potentially override this attribute to modify
# the test logic.
"_script": attr.label(allow_single_file=True,
default=Label("//mypkg:myrule_validator"))},
test = True,
)
//mypkg/BUILD
:
...
myrule(
name = "mytarget",
)
...
# Needed just once, to expose the template. Could have also used export_files(),
# and made the _script attribute set allow_files=True.
filegroup(
name = "myrule_validator",
srcs = [":myrule_validator.sh.template"],
)
# Needed for each target whose artifacts are to be checked. Notice that you no
# longer have to specify the output file name in a data attribute, or its
# $(location) expansion in an args attribute, or the label for the script
# (unless you want to override it).
myrule_validation_test(
name = "validate_mytarget",
target = ":mytarget",
)
テンプレートの展開アクションを使用する代わりに、
テンプレートが .bzl ファイルに文字列としてインライン化され、
str.format
メソッドまたは %
形式を使用した分析フェーズ。
Starlark ユーティリティのテスト
Skylib
unittest.bzl
フレームワークを使用して、ユーティリティ関数(つまり、
(マクロもルールも実装されません)。unittest.bzl
の
analysistest
ライブラリ(unittest
を使用できます)このようなテストスイートの場合、
コンビニエンス関数 unittest.suite()
を使用すると、ボイラープレートを削減できます。
//mypkg/myhelpers.bzl
:
def myhelper():
return "abc"
//mypkg/myhelpers_test.bzl
:
load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest")
load(":myhelpers.bzl", "myhelper")
def _myhelper_test_impl(ctx):
env = unittest.begin(ctx)
asserts.equals(env, "abc", myhelper())
return unittest.end(env)
myhelper_test = unittest.make(_myhelper_test_impl)
# No need for a test_myhelper() setup function.
def myhelpers_test_suite(name):
# unittest.suite() takes care of instantiating the testing rules and creating
# a test_suite.
unittest.suite(
name,
myhelper_test,
# ...
)
//mypkg/BUILD
:
load(":myhelpers_test.bzl", "myhelpers_test_suite")
myhelpers_test_suite(name = "myhelpers_tests")
その他の例については、Skylib 独自のテストをご覧ください。