简体   繁体   English

Python BDD 工具“行为”的 Bazel 测试规则

[英]Bazel test rule for the Python BDD tool "behave"

Question:问题:

Hello, dear bazel heroes!你好,亲爱的 bazel 英雄们!

For our system tests, I am trying to get the behavior-driven development tool behave to run with bazel as对于我们的系统测试,我试图让行为驱动的开发工具与bazel一起运行

  • bazel test system/acceptance_criteria:nice

What needs be accomplished:需要完成什么:

  • calling the command line behave <feature_file> (solved by filtering runfiles and using <feature_directory> instead of <feature_file> )调用命令行behave <feature_file> (通过过滤运行文件并使用<feature_directory>而不是<feature_file>来解决)
  • adding the folder steps to the runfiles (solved by explicitly prefixing a file with the path)将文件夹steps添加到运行文件(通过在文件前明确添加路径前缀来解决)
  • adding relevant Python step files to the runfiles (solved)将相关的 Python 步骤文件添加到运行文件(已解决)
  • adding the system under test to the PYTHONPATH (open)将被测系统添加到 PYTHONPATH(打开)

What would you recommend or do you have any other suggestions on how to solve this?您会推荐什么或者您对如何解决此问题有任何其他建议?

  • using Skylark for a new rule (I've tried that but struggling with PYTHONPATH and runfiles. I am getting a ModuleNotFoundError: No module named 'my_system' )将 Skylark 用于新规则(我已经尝试过了,但在 PYTHONPATH 和运行文件中苦苦挣扎。我收到ModuleNotFoundError: No module named 'my_system'
  • using Skylark for a macro with a genrule将 Skylark 用于具有 genrule 的宏
  • calling a script within a py_test (a possible but not so flexible workaround since I did not figure out how to pass parameters from bazel to the script)py_test中调用脚本(一种可能但不太灵活的解决方法,因为我没有弄清楚如何将参数从 bazel 传递到脚本)
  • some other best practice?其他一些最佳实践?

Cheers, Chris干杯,克里斯

If possible, provide a minimal example to reproduce the problem:如果可能,请提供一个最小示例来重现该问题:

What I've tried was pretty much copying the behavior of a py_test in Skylark and letting it run the command behave <feature_directory> or alternatively python -m behave <feature_directory> :我尝试的是几乎复制 Skylark 中py_test的行为并让它运行命令behave <feature_directory>或者python -m behave <feature_directory>

project/

  • WORKSPACE (empty) WORKSPACE (空)
  • system/
    • BUILD
    • my_system.py
    • acceptance_criteria/
      • BUILD
      • nice.feature
      • steps/
        • reusable_steps.py
  • tools/
    • BUILD (empty) BUILD (空)
    • behave_rule.bzl
    • bazel.rc

system/BUILD : system/BUILD

py_binary(
    name = "main",
    main = "my_system.py",
    srcs = ["my_system.py"],
    # deps = [], excluded for this example
    imports = ["."], # needed for adding PYTHONPATH in acceptance_criteria
    visibility = ["//system/acceptance_criteria:__pkg__"]
)

system/my_system.py : system/my_system.py

class MySystem():
    def yeah(self):
        return "yeah it works"

system/acceptance_criteria/BUILD : system/acceptance_criteria/BUILD

load("//tools:behave_rule.bzl", "py_bdd_test")

py_bdd_test(
    name = "nice",
    feats = [
        "nice.feature",
    ],
    steps = [
        "steps/reusable_steps.py",
    ],
    deps = [
        # TODO figure out how to add the implicit imports to the PYTHONPATH
        "//system:main",
    ],
    size = "small",
)

system/acceptance_criteria/nice.feature : system/acceptance_criteria/nice.feature

Feature: Multiprocess software

Scenario: 4 processes
  Given the device is powered on

steps/reusable_steps.py : steps/reusable_steps.py

from behave import *
from my_system import MySystem

@Given("the device is powered on")
def step_impl(context):
    raise NotImplementedError(MySystem().yeah())

tools/behave_rule.bzl : tools/behave_rule.bzl

# =============================================================================
# Description: Adds a test rule for the BDD tool behave to the bazel rule set.
# Knowledge:
# * https://bazel.build/versions/master/docs/skylark/cookbook.html
# * https://bazel.build/versions/master/docs/skylark/rules.html
# * https://bazel.build/versions/master/docs/skylark/lib/ctx.html
# * http://pythonhosted.org/behave/gherkin.html
# =============================================================================

"""Private implementation of the rule py_bdd_test.
"""
def _rule_implementation(ctx):

  # Store the path of the first feature file
  features_dir = ctx.files.feats[0].dirname

  # We want a test target so make it create an executable output.
  # https://bazel.build/versions/master/docs/skylark/rules.html#test-rules
  ctx.file_action(
      # Access the executable output file using ctx.outputs.executable.
      output=ctx.outputs.executable,
      content="behave %s" % features_dir,
      executable=True
  )
  # The executable output is added automatically to this target.

  # Add the feature and step files for behave to the runfiles.
  # https://bazel.build/versions/master/docs/skylark/rules.html#runfiles
  return [DefaultInfo(
      # Create runfiles from the files specified in the data attribute.
      # The shell executable - the output of this rule - can use them at runtime.
      # It is also possible to define data_runfiles and default_runfiles.
      # However if runfiles is specified it's not possible to define the above
      # ones since runfiles sets them both.
      runfiles = ctx.runfiles(
          files = ctx.files.feats + ctx.files.steps + ctx.files.deps)
  )]

"""An example documentation.

Args:
  name:
    A unique name for this rule.
  feats:
    Feature files used to run this target.
  steps:
    Files containing the mapping of feature steps to actual system API calls.
    Note: Since this rule implicitely uses the BDD tool "behave" they must
be in the "steps" folder (https://pythonhosted.org/behave/gherkin.html).
  deps:
    System to test.
"""
py_bdd_test = rule(
    implementation=_rule_implementation,
    attrs={
      # Do not declare "name": It is added automatically.
      "feats": attr.label_list(allow_files=True),
      "steps": attr.label_list(allow_files=True),
      "deps":
        attr.label_list(
            mandatory=True,
            non_empty=True,)
    },
    test=True,
)

tools/bazel.rc : tools/bazel.rc

test --test_output=errors

Environment info环境信息

  • Operating System: Windows 10 with docker (see Dockerfile below)操作系统:Windows 10 和 docker(见下文 Dockerfile)
  • Bazel version (output of bazel info release ): release 0.5.1rc1 Bazel 版本( bazel info release的输出):release 0.5.1rc1

Dockerfile : Dockerfile :

FROM ubuntu:xenial

# Install essentials
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update \
  && apt-get install -y gnupg git \
  && apt-get clean

# -----------------------------------------------------------------------------

# Install the awesome build automation tool bazel
# https://bazel.build/
# version > 0.4.5 since it has a bug in the rule extension skylark
RUN echo "deb http://storage.googleapis.com/bazel-apt testing jdk1.8" \
  > /etc/apt/sources.list.d/bazel.list
RUN apt-key adv --keyserver pool.sks-keyservers.net --recv-key 3D5919B448457EE0
RUN apt-get update \
  && apt-get install -y openjdk-8-jdk bazel \
  && apt-get clean \
  && rm -rf /var/lib/apt/lists/* && mkdir /var/lib/apt/lists/partial
ENV DEBIAN_FRONTEND ""

# Setup environment for bazel
ENV JAVA_HOME /usr/lib/jvm/java-8-openjdk-amd64

# Run bazel a first time for it to self-extract
RUN /usr/bin/bazel version

# -----------------------------------------------------------------------------

# Install Python (using the non-default version 3.6)
RUN echo "deb http://ppa.launchpad.net/jonathonf/python-3.6/ubuntu xenial main" \
  > /etc/apt/sources.list.d/jonathonf-ubuntu-python-3_6-xenial.list \
  && apt-key adv --keyserver pool.sks-keyservers.net --recv-key 8CF63AD3F06FC659 \
  && apt-get update \
  && apt-get install -y python3.6

# Create symlink from python3.6 to python
RUN ln -s /usr/bin/python3.6 /usr/local/bin/python

# Install the Python package manager pip (for the non-default version 3.6)
# https://en.wikipedia.org/wiki/Pip_(package_manager)
RUN apt-get update \
  && apt-get install -y wget \
  && wget https://bootstrap.pypa.io/get-pip.py \
  && python3.6 get-pip.py \
  && rm get-pip.py \
  && pip install --upgrade pip

# -----------------------------------------------------------------------------

# Install the Gherkin-based BDD tool "behave" for Python
RUN pip install behave

# -----------------------------------------------------------------------------

# Our build environment is based on bazel. Now run tests with it.
ENTRYPOINT "/usr/bin/bazel"

Have you found anything relevant by searching the web?您是否通过搜索 web 找到了相关内容?

Unfortunately not.不幸的是没有。 As mentioned in the comment of the behave_rule.bzl I looked into正如我调查过的behave_rule.bzl的评论中提到的

  • https://bazel.build/versions/master/docs/skylark/cookbook.html
  • https://bazel.build/versions/master/docs/skylark/rules.html
  • https://bazel.build/versions/master/docs/skylark/lib/ctx.html

and I've also found我还发现

  • https://github.com/bazelbuild/bazel/issues/702

Anything else, information or logs or outputs that would be helpful?还有什么有用的信息、日志或输出吗?

The command line output:命令行 output:

root@eec1fa791491:/project# bazel test system/acceptance_criteria:nice
INFO: Found 1 test target...
FAIL: //system/acceptance_criteria:nice (see /root/.cache/bazel/_bazel_root/2ca1f4ebdc59348ffdc31d97a51a98d5/execroot/project/bazel-out/local-fastbuild/testlogs/system/acceptance_criteria/nice/test.log).
INFO: From Testing //system/acceptance_criteria:nice:
==================== Test output for //system/acceptance_criteria:nice:
Exception ModuleNotFoundError: No module named 'my_system'
Traceback (most recent call last):
  File "/usr/local/bin/behave", line 11, in <module>
    sys.exit(main())
  File "/usr/local/lib/python3.6/dist-packages/behave/__main__.py", line 109, in main
    failed = runner.run()
  File "/usr/local/lib/python3.6/dist-packages/behave/runner.py", line 672, in run
return self.run_with_paths()
File "/usr/local/lib/python3.6/dist-packages/behave/runner.py", line 678, in run_with_paths
    self.load_step_definitions()
File "/usr/local/lib/python3.6/dist-packages/behave/runner.py", line 658, in load_step_definitions
    exec_file(os.path.join(path, name), step_module_globals)
File "/usr/local/lib/python3.6/dist-packages/behave/runner.py", line 304, in exec_file
    exec(code, globals, locals)
File "system/acceptance_criteria/steps/reusable_steps.py", line 5, in <module>
    from my_system import MySystem
ModuleNotFoundError: No module named 'my_system'
================================================================================
Target //system/acceptance_criteria:nice up-to-date:
bazel-bin/system/acceptance_criteria/nice
INFO: Elapsed time: 0.538s, Critical Path: 0.22s
//system/acceptance_criteria:nice                                        FAILED in 0.2s
/root/.cache/bazel/_bazel_root/2ca1f4ebdc59348ffdc31d97a51a98d5/execroot/project/bazel-out/local-fastbuild/testlogs/system/acceptance_criteria/nice/test.log

Executed 1 out of 1 test: 1 fails locally.

Originated from:来源于:

After some years have passed, sat down for a while and worked out this几年过去了,坐下来解决了这个问题

bdd.bzl

"""Custom Bazel Starlark rule for running behave tests.

Idea taken from https://github.com/bazelbuild/examples/
Also read https://docs.bazel.build/versions/main/skylark/concepts.html
"""

def _bdd_test_impl(ctx):
    # Assemble the command to run.
    bdd_tool = 'behave'
    default_args = '--no-capture --no-capture-stderr --no-logcapture'
    # Optional arguments are passed either
    #  explicitly as --test_arg=--foo="bar bar" (no space in between)
    #  or implicitly via ctx.attr.args
    optional_args = '"$@"'

    feature_file = ctx.file.main.basename
    features_path = ctx.label.package

    command = ' '.join([bdd_tool, default_args, features_path, '--include', feature_file])
    command = ' '.join([
        bdd_tool, features_path, '--include', feature_file, default_args, optional_args
    ])

    # Wrap an executable around the command.
    ctx.actions.write(
        output = ctx.outputs.executable,
        content = command,
    )

    # Ensure the files needed by the command are available at runtime
    srcs = ctx.files.main + ctx.files.steps + ctx.files.data
    deps = []
    for dep in ctx.attr.deps:
        # Collecting the files from the dependency tree is a little more hassle
        # since not well-documented and prone to change between bazel versions.
        deps += dep.default_runfiles.files.to_list()

    return [DefaultInfo(
        runfiles = ctx.runfiles(
            files = srcs + deps,
        ),
    )]

bdd_test = rule(
    implementation = _bdd_test_impl,
    test = True,
    attrs = {
        "main": attr.label(
            allow_single_file = [".feature"],
            mandatory = True,
        ),
        "steps": attr.label_list(
            allow_files = True,
        ),
        "deps": attr.label_list(),
        "data": attr.label_list(
            allow_files = True,
        ),
    },
)

BUILD

load("bdd.bzl", "bdd_test")

[bdd_test(
    name = filename[:filename.rfind(".feature")],
    main = filename,
    steps = ["environment.py"] + glob(["steps/*.py"]),
    deps = [
        # ':some_fixture',
    ],
) for filename in glob(["*.feature"])]

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM