Test

Sorun bildir Kaynağı göster Gece · 7,3 · 7,2 · 7,1 · 7,0 · 6,5

Starlark kodunu Bazel'de test etmek için çeşitli yaklaşımlar vardır. Bu sayfası, kullanım alanına göre güncel en iyi uygulamaları ve çerçeveleri toplar.

Kuralları test etme

Skylib'te unittest.bzl kuralların analiz zamanı davranışını (ör. eylemleri ve sağlayıcılar. 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 değil. Test tarafından oluşturulan hedefler, diğer testlerdeki veya derlemedeki hedeflerle çakıştığını unutmayın. Şu hata sırasında gerçekleşen kırıklık yerine Bazel'in bir yapı kopması olarak test hatasıdır.

  • Test edilen kuralları oluşturmak için yeterli miktarda ortak metin test onayları içeren kurallar. Bu ortak metin tıklayın. Makroların, dönüşüm hacminin 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 amacı oldukça küçük ve hafiftir. Belirli analiz testi çerçevesinin özellikleri, test sürecinin maksimum sayıda geçişli bağımlılığı olan hedefler (şu anda 500). Bu durum, söz konusu özelliklerin daha büyük ve ayrıntılı şekilde kullanılmasının performans üzerindeki testler.

Temel ilke, kontrol paneline bağlı bir test kuralı tanımlamaktır. testinin altında kalan bir kural var. Bu, test kuralının altındaki testin sağlayıcılar.

Test kuralının uygulama işlevi, onaylamaları yürütür. Varsa herhangi bir hata oluşursa bunlar fail() çağrısı uygulanarak hemen bildirilmez (bu çağrı bir analiz zamanı derleme hatası tetikleme) değil, bu tür hataları 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.

Minimum ö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.

İlk load() ifadelerinin dışında, dosya:

  • Testlerin kendisi ve her biri şundan oluşur: 1) analiz zamanı 2) kural için uygulama fonksiyonu, 2) analysistest.make() aracılığıyla test kuralı ve 3) yükleme süresi işlevi (makro) kullanarak, düşük testi (ve bağımlılıklarını) açıklamak ve test etmek için kuralı. Onaylar test durumları arasında değişmiyorsa 1) ve 2) birden fazla test durumu tarafından paylaşılmaktadır.

  • Her biri için yükleme süresi fonksiyonlarını çağıran test paketi işlevi test eder ve tüm testleri birlikte gruplandıran bir test_suite hedefi bildirir.

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, 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ır

  • bu kural türündeki hedefin etiketi foo_test olmalıdır (provider_contents_test)

  • test kuralının uygulama işlevi _foo_test_impl (_provider_contents_test_impl)

  • test edilen kuralların hedeflerinin etiketleri ve bağımlılıkları önünde foo_ (provider_contents_) öneki bulunmalıdır

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 koşullar altında durumu. Bunu analiz test çerçevesi kullanılarak yapılabilir:

analysistest.make ile oluşturulan test kuralı expect_failure değerini 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, paketinizde :all kullanarak tüm hedefleri derlemek kasıtlı olarak başarısız olan hedefin oluşturulmasını sağlar ve bir yapı hatası sergiler. Entegre "manuel" olarak ayarlarsanız test edilen hedefiniz, yalnızca açıkça belirtildiğinde veya manuel olmayan bir hedefin (test kuralınız gibi) bağımlılığı:

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

Kullanıcı tarafından gerçekleştirilen işlemler hakkında iddialar içeren testler kural kayıtlarından bazılarını ele alalım. (örneğin, ctx.actions.run() kullanılarak). 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 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, testi tanımlarken istenen derleme işaretlerini belirterek çözülebilir kural:

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ının değerleri geçersiz kılınır seçenekleri vardı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:

  • Shell, Python veya başka bir dilde test komut dosyası yazabilirsiniz ve uygun *_test kural türünde bir hedef oluşturur.

  • Yapmak istediğiniz testin türü için özel bir kural kullanabilirsiniz.

Test hedefi kullanma

Bir yapıyı doğrulamanın en dolaysız yolu komut dosyası yazmak BUILD dosyanıza bir *_test hedefi ekleyin. İ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. Daha fazla yönlendirme ve Starlark ancak BUILD dosyalarının daha temiz olmasını sağlar. Ayrıca, herhangi bir argüman, ön işleme, komut dosyası yerine Starlark’ta yapılabilir. (Sembolik yer tutucular kullandığından (örneğin, ikameler için) kullanın.

//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 bir dize olarak satır içi yaptı ve analiz aşamasına geçerken str.format yöntemini veya % biçimlendirmesini kullanabilirsiniz.

Starlark kamu hizmetlerini test etme

Skylib'in unittest.bzl çerçeve, yardımcı program fonksiyonlarını (yani ne makrolar ne de kural uygulamaları). unittest.bzl yerine analysistest kitaplığı, unittest kullanılabilir. Bu tür test paketlerinde unittest.suite() kolaylık işlevi, ortak metni azaltmak için 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.