পরীক্ষামূলক

ব্যাজেলে স্টারলার্ক কোড পরীক্ষা করার জন্য বিভিন্ন পদ্ধতি রয়েছে। এই পৃষ্ঠাটি ব্যবহারের ক্ষেত্রে বর্তমান সেরা অনুশীলন এবং কাঠামো সংগ্রহ করে।

পরীক্ষার নিয়ম

Skylib- এর একটি টেস্ট ফ্রেমওয়ার্ক রয়েছে unittest.bzl নামক নিয়মগুলির বিশ্লেষণ-সময় আচরণ যেমন তাদের ক্রিয়াকলাপ এবং প্রদানকারীদের পরীক্ষা করার জন্য। এই ধরনের পরীক্ষাগুলিকে "বিশ্লেষণ পরীক্ষা" বলা হয় এবং বর্তমানে নিয়মের অভ্যন্তরীণ কাজগুলি পরীক্ষা করার জন্য সেরা বিকল্প।

কিছু সতর্কতা:

  • পরীক্ষার দাবী বিল্ডের মধ্যে ঘটে, একটি পৃথক পরীক্ষা রানার প্রক্রিয়া নয়। পরীক্ষার দ্বারা তৈরি করা লক্ষ্যগুলিকে এমনভাবে নামকরণ করতে হবে যাতে তারা অন্যান্য পরীক্ষা বা বিল্ডের লক্ষ্যগুলির সাথে সংঘর্ষ না করে। পরীক্ষার সময় ঘটে যাওয়া একটি ত্রুটিকে ব্যাজেল পরীক্ষা ব্যর্থতার পরিবর্তে একটি বিল্ড ব্রেকেজ হিসাবে দেখে।

  • পরীক্ষার অধীনে নিয়মগুলি এবং পরীক্ষার দাবি সম্বলিত নিয়মগুলি সেট আপ করার জন্য ন্যায্য পরিমাণে বয়লারপ্লেট প্রয়োজন৷ এই বয়লারপ্লেট প্রথমে ভয়ঙ্কর মনে হতে পারে। এটি মনে রাখতে সাহায্য করে যে ম্যাক্রো মূল্যায়ন করা হয় এবং লোডিং পর্বের সময় লক্ষ্যগুলি তৈরি করা হয়, যখন নিয়ম বাস্তবায়ন ফাংশনগুলি বিশ্লেষণ পর্বের সময় পরে পর্যন্ত চালানো হয় না।

  • বিশ্লেষণ পরীক্ষা মোটামুটি ছোট এবং হালকা হতে উদ্দেশ্য করা হয়. বিশ্লেষণ পরীক্ষার কাঠামোর কিছু বৈশিষ্ট্য সর্বাধিক সংখ্যক ট্রানজিটিভ নির্ভরতা (বর্তমানে 500) সহ লক্ষ্য যাচাই করার জন্য সীমাবদ্ধ। এটি বড় পরীক্ষার সাথে এই বৈশিষ্ট্যগুলি ব্যবহার করার কার্যকারিতার প্রভাবের কারণে।

মৌলিক নীতি হল একটি পরীক্ষার নিয়ম সংজ্ঞায়িত করা যা নিয়ম-আন্ডার-টেস্টের উপর নির্ভর করে। এটি নিয়ম-আন্ডার-টেস্ট প্রদানকারীদের পরীক্ষার নিয়ম অ্যাক্সেস দেয়।

পরীক্ষার নিয়মের বাস্তবায়ন ফাংশন দাবী বহন করে। যদি কোন ব্যর্থতা থাকে, তাহলে 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)

এছাড়াও নিশ্চিত করুন যে পরীক্ষার অধীনে আপনার লক্ষ্য বিশেষভাবে 'ম্যানুয়াল' ট্যাগ করা হয়েছে। এটি ছাড়া, :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: , যেমন উপরে দেখানো হয়েছে প্রিফিক্স করা আবশ্যক।

নিদর্শন যাচাই করা হচ্ছে

আপনার তৈরি করা ফাইলগুলি সঠিক কিনা তা পরীক্ষা করার প্রধান উপায়গুলি হল:

  • আপনি শেল, পাইথন বা অন্য ভাষায় একটি পরীক্ষা স্ক্রিপ্ট লিখতে পারেন এবং উপযুক্ত *_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"],
)

একটি কাস্টম নিয়ম ব্যবহার করে

একটি আরও জটিল বিকল্প হল শেল স্ক্রিপ্টটিকে একটি টেমপ্লেট হিসাবে লেখা যা একটি নতুন নিয়ম দ্বারা তাত্ক্ষণিক হয়। এতে আরও ইনডাইরেকশান এবং স্টারলার্ক লজিক জড়িত, কিন্তু ক্লিনার বিল্ড ফাইলের দিকে নিয়ে যায়। একটি পার্শ্ব-সুবিধা হিসাবে, স্ক্রিপ্টের পরিবর্তে স্টারলার্ক-এ যেকোনো আর্গুমেন্ট প্রিপ্রসেসিং করা যেতে পারে এবং স্ক্রিপ্টটি কিছুটা বেশি স্ব-নথিভুক্ত কারণ এটি সাংখ্যিক (আর্গুমেন্টের জন্য) পরিবর্তে প্রতীকী স্থানধারক (প্রতিস্থাপনের জন্য) ব্যবহার করে।

//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 পদ্ধতি বা % -ফরম্যাটিং ব্যবহার করে বিশ্লেষণ পর্বের সময় এটিকে প্রসারিত করতে পারতেন।

স্টারলার্ক ইউটিলিটি পরীক্ষা করা হচ্ছে

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 এর নিজস্ব পরীক্ষা দেখুন।