Bazel'de Starlark kodunu test etmenin birkaç farklı yolu vardır. Bu sayfası, kullanım alanına göre güncel en iyi uygulamaları ve çerçeveleri toplar.
Kuralları test etme
Skylib'de, kuralların analiz sırasındaki davranışını (ör. işlemleri ve sağlayıcıları) kontrol etmek için unittest.bzl
adlı bir test çerçevesi bulunur. Bu tür testler "analiz testleri" olarak adlandırılır ve şu anda en iyi
seçenekleri vardır.
Dikkat edilmesi gereken bazı noktalar:
Test onayları derleme içinde gerçekleşir, ayrı bir test çalıştırıcı işleminde gerçekleşmez. 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 görülür.
Test edilen kuralları oluşturmak için yeterli miktarda ortak metin test onayları içeren kurallar. Bu şablon ilk başta göz korkutucu görünebilir. Makroların, dönüşüm hacmine yükleme aşamasında değerlendirilir ve hedefler oluşturulurken, kural, ve uygulama işlevleri analiz aşamasında sonrakie kadar çalışmaz.
Analiz testlerinin oldukça küçük ve hafif olması amaçlanır. Belirli analiz test çerçevesinin özellikleri, test sürecinin maksimum sayıda geçişli bağımlılığı olan hedefler (şu anda 500). Bunun nedeni, bu özelliklerin daha büyük testlerle kullanılmasının performans üzerindeki etkileridir.
Temel prensip, test edilen kurala bağlı bir test kuralı tanımlamaktır. Bu, test kuralının test edilen kuralın sağlayıcılarına erişmesine olanak tanır.
Test kuralının uygulama işlevi, onaylamaları yürütür. Varsa
herhangi bir hata oluşursa fail()
çağrılarak bunlar hemen bildirilmez (bu çağrı
bir analiz zamanı derleme hatasını tetiklemek) yerine, hataları bir
komut dosyası test amacıyla çalıştırıldığında başarısız olur.
Minimal bir oyuncak örneği ve ardından işlemleri kontrol eden bir örnek için aşağıya bakın.
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_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")
Test, bazel test //mypkg:myrules_test
ile çalıştırılabilir.
Dosyanın ilk load()
beyanları dışında iki ana bölümü vardır:
Testlerin kendisi. Her biri 1) test kuralı için analiz zamanındaki bir uygulama işlevi, 2)
analysistest.make()
aracılığıyla test kuralının beyanı ve 3) test edilen kuralı (ve bağımlılıkları) ve test kuralını beyan etmek için yükleme zamanındaki bir işlev (makro) içerir. Test durumları arasında iddialar değişmezse 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 açıklayan test paketi işlevi.
Tutarlılık için, önerilen adlandırma kuralını uygulayın: foo
yerine
test adının, testin neyi kontrol ettiğini açıklayan bölümü
(yukarıdaki örnekte provider_contents
). Örneğin, bir JUnit test yöntemi
testFoo
olarak adlandırılır.
Ardından:
testi ve test edilen hedefi oluşturan makronun adı
_test_foo
(_test_provider_contents
)test kuralı türü
foo_test
(provider_contents_test
) olarak adlandırılmalıdırBu 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 etiketleri ve bunların bağımlılıkları
foo_
(provider_contents_
) ile öneklendirilmelidir.
Tüm hedeflerin etiketlerinin aynı içindeki diğer etiketlerle çakışabileceğini unutmayın BUILD paketi olduğundan test için benzersiz bir ad kullanmanız yararlı olur.
Hata testi
Bir kuralın, belirli girdiler veya belirli düzenlemeler nedeniyle başarısız olduğunu doğrulamak, durumu. 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ı, hatanın yapısı hakkında onaylamalar yapmalıdır. (özellikle, başarısızlık mesajı):
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 özel olarak "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" seçeneğiyle, test edilen hedefiniz yalnızca açıkça belirtilirse veya manuel olmayan bir hedefin (test kuralınız gibi) 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
# ":failure_testing_test" to the suite's test targets.
Kayıtlı işlemleri doğrulama
Kuralınızın kaydettiği işlemler hakkında iddialarda bulunan testler (ör. ctx.actions.run()
kullanarak) yazabilirsiniz. Bu işlem,
analiz test kuralı uygulama fonksiyonu. Ö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)
işlevinin
tarafından kaydedilen işlemleri temsil eden Action
nesneleri
hedef test ediliyor.
Farklı işaretler altında kural davranışını doğrulama
Gerçek kuralınızın belirli bir yapıda belirli bir şekilde davrandığını doğrulamak isteyebilirsiniz. işaretidir. Örneğin, bir kullanıcı şunu belirtirse kuralınız farklı şekilde davranabilir:
bazel build //mypkg:real_target -c opt
karşı
bazel build //mypkg:real_target -c dbg
Bu, ilk bakışta, test edilen hedefi istenen derleme işaretleri:
bazel test //mypkg:myrules_test -c opt
Ancak test paketinizde aynı anda bir
Bu test, -c opt
altında kural davranışını doğrulayan başka bir test ve
-c dbg
altındaki kural davranışını doğrular. Her iki test de çalıştırılamazdı
aynı derlemede bulunuyor.
Bu sorun, test kuralını tanımlarken istenen derleme işaretlerini belirterek çözülebilir:
myrule_c_opt_test = analysistest.make(
_myrule_c_opt_test_impl,
config_settings = {
"//command_line_option:compilation_mode": "opt",
},
)
Normalde test edilen 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. (Belirtilmemiş tüm seçenekler, değerlerini,
komut satırından).
Belirtilen config_settings
sözlüğünde komut satırı işaretleri
gösterildiği gibi özel bir yer tutucu değeri //command_line_option:
ile başlar
bölümünü ziyaret edin.
Yapıları doğrulama
Oluşturulan dosyaların doğru olup olmadığını kontrol etmenin başlıca yolları şunlardır:
Kabuk, Python veya başka bir dilde bir test komut dosyası yazabilir ve uygun
*_test
kural türünü içeren 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 kolay yolu, bir komut dosyası yazıp BUILD dosyanıza *_test
hedefi eklemektir. İncelemek istediğiniz belirli eserler
bu hedefin veri bağımlılıkları olmalıdır. Doğrulama mantığınız
tekrar kullanılabiliyorsa, komut satırı alan bir komut dosyası olmalıdır
test hedefinin args
özelliği tarafından kontrol edilen bağımsız değişkenler. Bir
yukarıdaki myrule
çıkışının "abc"
olduğunu doğrulayan örnek.
//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"],
)
Özel kural kullanma
Daha karmaşık bir alternatif ise kabuk komut dosyasını, yeni bir kural tarafından örneklendirilir. Bu yöntem daha fazla dolaylı yöntem ve Starlark mantığı içerir ancak daha temiz BUILD dosyalarına yol açar. Bu yaklaşımın yan faydası olarak, tüm ön işleme işlemleri komut dosyası yerine Starlark'ta yapılabilir. Ayrıca, komut dosyası sayısal (parametreler için) yerine sembolik yer tutucular (yerine koyma işlemleri için) kullandığından, komut dosyası biraz daha kendi kendine açıklayıcıdır.
//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",
)
Alternatif olarak, şablon genişletme işlemi kullanmak yerine şablonu .bzl dosyasına dize olarak satır içi olarak ekleyebilir ve analiz aşamasında str.format
yöntemini veya %
biçimlendirmesini kullanarak genişletebilirsiniz.
Starlark kamu hizmetlerini test etme
Skylib'in unittest.bzl
çerçevesi, yardımcı işlevleri (yani makro veya kural uygulaması olmayan işlevler) test etmek için kullanılabilir. unittest.bzl
yerine
analysistest
kitaplığı, unittest
kullanılabilir. Bu tür test paketlerinde, ortak metinleri 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.