'How do I use pytest with bazel?

I have a my_module.py file that implements my_module and a file test_my_module.py that does import my_module and runs some tests written with pytest on it.

Normally I run the tests by cding into the directory that contains these two files and then doing

pytest

Now I want to use Bazel. I've added my_module.py as a py_binary but I don't know what the right way to invoke my tests is.



Solution 1:[1]

Add the following code to test_my_module.py and mark the test script as a py_test instead of py_binary in your BUILD file:

if __name__ == "__main__":
    import pytest
    raise SystemExit(pytest.main([__file__]))

Then you can run your tests with bazel test test_my_module

Solution 2:[2]

If you want to create a reusable code, that don't need add call to pytest add end of every python file with test. You can create py_test call that call a python file wrapping a call to pytest and keeping all argument. Then create a macro around the py_test. I explain the detailed solution in Experimentations on Bazel: Python (3), linter & pytest, with link to source code.

Create the python tool (wrapp call to pytest, or only pylint) in tools/pytest/pytest_wrapper.py

import sys
import pytest

# if using 'bazel test ...'
if __name__ == "__main__":
    sys.exit(pytest.main(sys.argv[1:]))


Create the macro in tools/pytest/defs.bzl

"""Wrap pytest"""

load("@rules_python//python:defs.bzl", "py_test")
load("@my_python_deps//:requirements.bzl", "requirement")

def pytest_test(name, srcs, deps = [], args = [], data = [], **kwargs):
    """
        Call pytest
    """
    py_test(
        name = name,
        srcs = [
            "//tools/pytest:pytest_wrapper.py",
        ] + srcs,
        main = "//tools/pytest:pytest_wrapper.py",
        args = [
            "--capture=no",
            "--black",
            "--pylint",
            "--pylint-rcfile=$(location //tools/pytest:.pylintrc)",
            # "--mypy",
        ] + args + ["$(location :%s)" % x for x in srcs],
        python_version = "PY3",
        srcs_version = "PY3",
        deps = deps + [
            requirement("pytest"),
            requirement("pytest-black"),
            requirement("pytest-pylint"),
            # requirement("pytest-mypy"),
        ],
        data = [
            "//tools/pytest:.pylintrc",
        ] + data,
        **kwargs
    )

expose some resources from tools/pytest/BUILD.bazel

exports_files([
    "pytest_wrapper.py",
    ".pylintrc",
])


Call it from your package BUILD.bazel

load("//tools/pytest:defs.bzl", "pytest_test")
...

pytest_test(
    name = "test",
    srcs = glob(["*.py"]),
    deps = [
        ...
    ],
)

then calling bazel test //... means that pylint, pytest and black are all part of the test flow.

Solution 3:[3]

Following on from @David Bernard, who wrote his answer in an awesome series of blog posts BTW, there is a curve-ball there with pytest + bazel + Windows...

Long story short, you'll need to add legacy_create_init = 0 to the py_test rule call.

This is a workaround a "feature" where bazel will create __init__.py files in the sandbox, even when none were present in your repo https://github.com/bazelbuild/rules_python/issues/55

Solution 4:[4]

It seems a bunch of the suggestions here have been packaged up into https://github.com/caseyduquettesc/rules_python_pytest now.

load("@rules_python_pytest//python_pytest:defs.bzl", "py_pytest_test")

py_pytest_test(
    name = "test_w_pytest",
    size = "small",
    srcs = ["test.py"],
    deps = [
      # TODO Add this for the user
      requirement("pytest"),
    ],
)

Edit: I'm the author of the above repository

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Boris Verkhovskiy
Solution 2 n1k31t4
Solution 3 jandom
Solution 4