Bazel'de Starlark kodunu test etmek için birkaç farklı yaklaşım vardır. Bu sayfada, kullanım alanına göre mevcut en iyi uygulamalar ve çerçeveler bir araya getirilmiştir.
Kuralları test etme
Skylib, kuralların analiz zamanındaki davranışını (ör. işlemleri ve sağlayıcıları) kontrol etmek için unittest.bzl
adlı bir test çerçevesine sahiptir. Bu tür testlere "analiz testleri" adı verilir ve şu anda kuralların iç işleyişini test etmek için en iyi seçenektir.
Dikkat edilmesi gereken bazı noktalar:
Test onayları ayrı bir test çalıştırıcı işleminde değil, derleme içinde gerçekleşir. Test tarafından oluşturulan hedefler, diğer testlerdeki veya derlemedeki hedeflerle çakışmayacak şekilde adlandırılmalıdır. Test sırasında oluşan bir hata, Bazel tarafından test hatası yerine derleme hatası olarak değerlendirilir.
Test edilen kuralları ve test onaylarını içeren kuralları ayarlamak için oldukça fazla standart kod gerekir. Bu standart metin ilk başta göz korkutucu görünebilir. Makroların yükleme aşamasında değerlendirildiği ve hedeflerin oluşturulduğu, kural uygulama işlevlerinin ise daha sonra, analiz aşamasında çalıştırıldığı unutulmamalıdır.
Analiz testleri oldukça küçük ve basit olacak şekilde tasarlanmıştır. Analiz testi çerçevesinin belirli özellikleri, maksimum sayıda geçişli bağımlılığı (şu anda 500) olan hedeflerin doğrulanmasıyla sınırlıdır. Bu kısıtlama, söz konusu özelliklerin daha büyük testlerle kullanılması durumunda performansın olumsuz etkilenmesi nedeniyle uygulanır.
Temel ilke, test edilen kurala bağlı bir test kuralı tanımlamaktır. Bu, test kuralına test edilen kuralın sağlayıcılarına erişim izni verir.
Test kuralının uygulama işlevi, onaylamaları gerçekleştirir. Herhangi bir hata varsa bunlar fail()
çağrılarak hemen bildirilmez (bu, analiz sırasında derleme hatasına neden olur). Bunun yerine, hatalar test yürütme sırasında başarısız olan oluşturulmuş bir komut dosyasında saklanır.
En basit örnek aşağıda verilmiştir. Ardından, işlemleri kontrol eden bir örnek verilmiştir.
Minimal örnek
//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_co
ntents()
# ...
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 r
eturn the result.
myrules_test_suite(name = "myrules_test")
Test, bazel test //mypkg:myrules_test
ile birlikte çalıştırılabilir.
İlk load()
ifadelerinin yanı sıra dosyanın iki ana bölümü vardır:
Testlerin kendisi. Bu testlerin her biri 1) test kuralı için analiz zamanı uygulama işlevi, 2)
analysistest.make()
aracılığıyla test kuralının bildirimi ve 3) test edilen kuralı (ve bağımlılıklarını) ve test kuralını bildirmek için yükleme zamanı işlevinden (makro) oluşur. Onaylamalar test durumları arasında değişmiyorsa 1) ve 2) birden fazla test durumu tarafından paylaşılabilir.Her test için yükleme süresi işlevlerini çağıran ve tüm testleri bir araya getiren bir
test_suite
hedefi bildiren test paketi işlevi.
Tutarlılık için önerilen adlandırma kuralını uygulayın: foo
, testin neyi kontrol ettiğini açıklayan test adı bölümünü (yukarıdaki örnekte provider_contents
) temsil etsin. Örneğin, bir JUnit test yöntemi testFoo
olarak adlandırılır.
Ardından:
Testi ve test edilen hedefi oluşturan makro
_test_foo
(_test_provider_contents
) olarak adlandırılmalıdır.Test kuralı türü
foo_test
(provider_contents_test
) olarak adlandırılmalıdır.Bu kural türünün hedefinin etiketi
foo_test
olmalıdır. (provider_contents_test
)Test kuralının uygulama işlevi
_foo_test_impl
(_provider_contents_test_impl
) olarak adlandırılmalıdır.Test edilen kuralların hedeflerinin ve bağımlılıklarının etiketleri
foo_
(provider_contents_
) ile başlamalıdır.
Tüm hedeflerin etiketlerinin aynı BUILD paketindeki diğer etiketlerle çakışabileceğini unutmayın. Bu nedenle, test için benzersiz bir ad kullanmak faydalı olur.
Arıza testi
Belirli girişler veya belirli bir durumda bir kuralın başarısız olduğunu doğrulamak faydalı olabilir. Bu işlem, analiz testi çerçevesi kullanılarak yapılabilir:
analysistest.make
ile oluşturulan test kuralı expect_failure
belirtmelidir:
failure_testing_test = analysistest.make(
_failure_testing_test_impl,
expect_failure = True,
)
Test kuralı uygulaması, gerçekleşen hatanın niteliği (özellikle hata mesajı) hakkında iddialarda bulunmalıdır:
def _failure_testing_test_impl(ctx):
env = analysistest.begin(ctx)
asserts.expect_failure(env, "This rule should never work")
return analysistest
.end(env)
Ayrıca, test edilen hedefinizin özellikle "manuel" olarak etiketlendiğinden emin olun.
Bu olmadan, paketinizdeki tüm hedefleri :all
kullanarak oluşturmak, kasıtlı olarak başarısız olan hedefin oluşturulmasına ve derleme hatası gösterilmesine neden olur. "Manuel" ile, test edilen hedefiniz yalnızca açıkça belirtilmişse veya manuel olmayan bir hedefin (ör. test kuralınız) bağımlılığı olarak oluşturulur:
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
# ":failur
e_testing_test" to the suite's test targets.
Kayıtlı işlemleri doğrulama
Kuralınızın kaydettiği işlemlerle ilgili onaylamalar yapan testler yazmak isteyebilirsiniz. Örneğin, ctx.actions.run()
kullanabilirsiniz. Bu işlem, analiz testi kuralı uygulama işlevinizde yapılabilir. Örnek:
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)
, test edilen hedef tarafından kaydedilen işlemleri temsil eden Action
nesnelerinin listesini döndürür.
Farklı işaretler altındaki kural davranışını doğrulama
Belirli derleme işaretleri verildiğinde gerçek kuralınızın belirli bir şekilde davrandığını doğrulamak isteyebilirsiniz. Örneğin, bir kullanıcı aşağıdakileri belirtirse kuralınız farklı şekilde davranabilir:
bazel build //mypkg:real_target -c opt
karşı
bazel build //mypkg:real_target -c dbg
İlk bakışta bu işlem, test edilen hedefi istenen derleme işaretleriyle test ederek yapılabilir:
bazel test //mypkg:myrules_test -c opt
Ancak bu durumda, test paketenizin aynı anda -c opt
altındaki kural davranışını doğrulayan bir test ve -c dbg
altındaki kural davranışını doğrulayan başka bir test içermesi imkansız hale gelir. Her iki test de aynı derlemede çalıştırılamaz.
Bu sorun, test kuralını tanımlarken istenen derleme işaretleri belirtilerek çözülebilir:
myrule_c_opt_test = analysistest.make(
_myrule_c_opt_test_impl,
config_settings = {
"//command_line_option:compilation_mode": "
opt",
},
)
Normalde, test edilen bir hedef mevcut derleme işaretleri göz önünde bulundurularak analiz edilir.
config_settings
belirtildiğinde, belirtilen komut satırı seçeneklerinin değerleri geçersiz kılınır. (Belirtilmeyen tüm seçenekler, gerçek komut satırındaki değerlerini korur.)
Belirtilen config_settings
sözlüğünde, komut satırı işaretlerine yukarıda gösterildiği gibi //command_line_option:
özel yer tutucu değeriyle önek eklenmelidir.
Yapıları doğrulama
Oluşturulan dosyalarınızın doğru olduğunu kontrol etmenin başlıca yolları şunlardır:
Shell, Python veya başka bir dilde test komut dosyası yazabilir ve uygun
*_test
kural türünde bir hedef oluşturabilirsiniz.Yapmak istediğiniz test türü için özel bir kural kullanabilirsiniz.
Test hedefi kullanma
Bir yapıyı doğrulamanın en basit yolu bir komut dosyası yazıp BUILD dosyanıza *_test
hedefi eklemektir. Kontrol etmek istediğiniz belirli yapılar, bu hedefin veri bağımlılıkları olmalıdır. Doğrulama mantığınız birden fazla test için yeniden kullanılabilirse test hedefinin args
özelliği tarafından kontrol edilen komut satırı bağımsız değişkenlerini alan bir komut dosyası olmalıdır. Yukarıdaki myrule
çıkışının "abc"
olduğunu doğrulayan bir örnek aşağıda verilmiştir.
//mypkg/myrule_validator.sh
:
if [ "$(cat $1)" = "abc" ]; then
echo "Passed"
exit 0
els
e
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)&
quot;],
data = [":mytarget.out"],
)
Özel kural kullanma
Daha karmaşık bir alternatif ise kabuk komut dosyasını yeni bir kural tarafından oluşturulan bir şablon olarak yazmaktır. Bu işlem daha fazla yönlendirme ve Starlark mantığı içerir ancak daha temiz BUILD dosyaları oluşturur. Yan avantaj olarak, herhangi bir bağımsız değişken ön işleme, komut dosyası yerine Starlark'ta yapılabilir ve komut dosyası, sayısal olanlar (bağımsız değişkenler için) yerine sembolik yer tutucular (değişiklikler için) kullandığından biraz daha fazla kendi kendini belgeleyebilir.
//mypkg/myrule_validator.sh.template
:
if [ "$(cat %TARGET%)" = "abc" ]; then
echo "Passed"
exit 0
els
e
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,
defa
ult=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_myta
rget",
target = ":mytarget",
)
Alternatif olarak, şablon genişletme işlemi kullanmak yerine şablonu .bzl dosyasına dize olarak yerleştirebilir ve str.format
yöntemi veya %
-biçimlendirmeyi kullanarak analiz aşamasında genişletebilirsiniz.
Starlark yardımcı programlarını test etme
Skylib'in
unittest.bzl
çerçevesi, yardımcı işlevleri (yani ne makro ne de kural uygulaması olan işlevler) test etmek için kullanılabilir. unittest.bzl
'nın analysistest
kitaplığı yerine unittest
kullanılabilir. Bu tür test paketlerinde, standart kodları azaltmak için unittest.suite()
kolaylık işlevi kullanılabilir.
//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")
Daha fazla örnek için Skylib'in kendi testlerine bakın.