如何从单个 java_test() 规则 运行 Bazel 中的所有测试?

How to run all tests in Bazel from a single java_test() rule?

我正在 Bazel 中添加测试,但我不想为每个测试文件编写测试规则。但是,每个测试规则都需要一个 test_class - 测试 class 即 运行,因此没有简单的方法来 运行 所有测试只用一个 [=15] =] 规则。是否可以解决我不需要指定 test_class 而只需要一次 运行 所有测试的问题?

您可以编写一个 JUnit 测试套件 class,它将 运行 您的其他测试。例如,如果你有测试 classes Test1.java 和 Test2.java,你可以这样做:

AllTests.java

import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;

@RunWith(Suite.class)
@SuiteClasses({
    Test1.class,
    Test2.class
})
public class AllTests {}

建造

java_test(
    name = "AllTests",
    test_class = "AllTests",
    srcs = [
        "AllTests.java",
        "Test1.java",
        "Test2.java",
    ],
)

编辑以回应评论:

如果您不想在测试套件中指定测试 class 名称,您可以通过反射来做一些事情。以下示例假设您所有的测试都在 "com.foo" 包中,并且所有测试都是 java_test 规则的 src:

package com.foo;

import java.io.File;
import java.io.IOException;
import java.net.URLClassLoader;
import java.util.Enumeration;
import java.util.Set;
import java.util.TreeSet;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import junit.framework.JUnit4TestAdapter;
import junit.framework.TestSuite;
import org.junit.runner.RunWith;

@RunWith(org.junit.runners.AllTests.class)
public class AllTests {
  public static TestSuite suite() throws IOException {
    TestSuite suite = new TestSuite();
    URLClassLoader classLoader = (URLClassLoader) Thread.currentThread().getContextClassLoader();
    // The first entry on the classpath contains the srcs from java_test
    findClassesInJar(new File(classLoader.getURLs()[0].getPath()))
        .stream()
        .map(c -> {
          try {
            return Class.forName(c);
          } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
          }
        })
        .filter(clazz -> !clazz.equals(AllTests.class))
        .map(JUnit4TestAdapter::new)
        .forEach(suite::addTest);
    return suite;
  }

  private static Set<String> findClassesInJar(File jarFile) {
    Set<String> classNames = new TreeSet<>();
    try {
      try (ZipFile zipFile = new ZipFile(jarFile)) {
        Enumeration<? extends ZipEntry> entries = zipFile.entries();
        while (entries.hasMoreElements()) {
          String entryName = entries.nextElement().getName();
          if (entryName.startsWith("com/foo") && entryName.endsWith(".class")) {
            int classNameEnd = entryName.length() - ".class".length();
            classNames.add(entryName.substring(0, classNameEnd).replace('/', '.'));
          }
        }
      }
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
    return classNames;
  }
}

Bazel, we wrote a custom Junit Suite that finds all the Junit classes on the classpath in or under the package of the annotated class. You can find the code here。它非常简短直接,您可以将其复制到您的项目中或做类似的事情。

然后您可以这样设置规则:

java_library(
    name = "tests",
    testonly = 1,
    srcs = glob(["*.java"])
)

java_test(
   name = "MyTests",
   test_class = "MyTests",
   runtime_deps = [":tests"],
)

和 MyTests.java 文件应如下所示:

import package.ClasspathSuite;

import org.junit.runner.RunWith;

@RunWith(ClasspathSuite.class)
public class MyTests { } 

这是一个不需要使用 Suite 的解决方案。 请记住,它将为每个 class.

单独生成覆盖率报告 .dat 文件

.bzl 宏来处理所有测试:

def run_tests(name, srcs, package, deps):
  for src in srcs:
    src_name = src[:-5]
    native.java_test(name=src_name, test_class=package + "." + src_name, srcs=srcs, deps=deps, size="small")

从测试文件位置调用该宏:

run_tests(
    name = "test",
    srcs = glob(["*Test.java"]),
    package = "pkg",
    deps = [
        ":src_lib",
    ]
)

Gerrit 项目包含一个名为 junit_tests 的 Starlark 函数。它获取一个 src 列表并生成一个 AllTestsTestSuite.java 文件,该文件 运行s 在每个 Java class 中进行测试。它还会生成一个 java_test 目标,其中包括生成的 java 文件和所有指定的源、deps 等。下面是如何设置它。

首先将这些行添加到您的 WORKSPACE 文件中:

load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")

# Re-usable building blocks for Bazel build tool
# https://gerrit.googlesource.com/bazlets/
# https://gerrit.googlesource.com/bazlets/+/968b97fa03a9d2afd760f2e8ede3d5643da390d2
git_repository(
    name = "com_googlesource_gerrit_bazlets",
    remote = "https://gerrit.googlesource.com/bazlets",
    commit = "968b97fa03a9d2afd760f2e8ede3d5643da390d2",
)
# We cannot use the tar.gz provided over HTTP because it contains timestamps and each download has a
# different hash.
#http_archive(
#    name = "com_googlesource_gerrit_bazlets",
#    sha256 = "...",
#    urls = [
#        "https://gerrit.googlesource.com/bazlets/+archive/968b97fa03a9d2afd760f2e8ede3d5643da390d2.tar.gz",
#    ],
#)
# This provides these useful imports:
# load("@com_googlesource_gerrit_bazlets//tools:maven_jar.bzl", "maven_jar")
# load("@com_googlesource_gerrit_bazlets//tools:junit.bzl", "junit_tests")

现在将此添加到您的 BUILD 文件中:

load("@com_googlesource_gerrit_bazlets//tools:junit.bzl", "junit_tests")

junit_tests(
    name = "AllTests",
    srcs = glob(["*.java"]),
    deps = [
        "//java/com/company/a_package",
        "@maven//:junit_junit",
        "@maven//:org_hamcrest_hamcrest",
    ],
)

如果您的 BUILD 文件位于 $WORKSPACE_ROOT/javatests/com/company/a_package/BUILD,那么您可以 运行 这些特定测试:

bazel test //javatests/com/company/a_package:AllTests

您可以 运行 所有 java 测试如下:

bazel test //javatests/...

如果您的目录包含 .java 没有测试的文件,AllTests 目标将失败并返回 "No runnable methods"。解决方法是向文件添加一个空测试:

/** Workaround for https://github.com/bazelbuild/bazel/issues/2539 */
@Test
public void emptyTest() {}

这适用于我在 MacOS 上使用 Bazel 2.0.0。我还可以 运行 使用 Bazel 插件在 IntelliJ 2019.2 中进行测试。