【问题标题】:Create assembly jar that contains all tests in SBT project+subprojects创建包含 SBT 项目+子项目中所有测试的程序集 jar
【发布时间】:2020-03-29 03:15:01
【问题描述】:

我有一个有趣的问题,我基本上需要创建一个 .jar(加上所有类路径依赖项),其中包含 SBT 项目的所有测试(加上它的任何子项目)。我的想法是我可以使用 java -jar 运行 jar,然后所有测试都会执行。

我听说这可能与 sbt-assembly 相关,但您必须为您拥有的每个 sbt 子项目手动运行 assembly(每个子项目都有自己的 .jars),理想情况下我只想运行一个命令,为您碰巧拥有的每个 sbt root+sub 项目中的每个测试生成一个巨大的 .jar(以同样的方式,如果您在具有子项目的 sbt 项目中运行 test,它将运行所有测试)。

我们当前使用的测试框架是 specs2,尽管我不确定这是否会有所不同。

有人知道这是否可行吗?

【问题讨论】:

    标签: scala sbt specs2 sbt-assembly sbt-native-packager


    【解决方案1】:

    不支持导出测试运行器

    sbt 1.3.x 没有这个功能。定义的测试与测试框架(如 Specs2)提供的运行器和 sbt 的构建一起执行,该构建也反射性地发现您定义的测试(例如,哪个类扩展了 Spec2 的测试特征?)。理论上,我们已经拥有了您需要的大部分内容,因为Test / fork := true 创建了一个名为ForkMain 的程序并在另一个JVM 中运行您的测试。缺少的是调度您定义的测试。

    使用 specs2.run 运行器

    谢天谢地,Specs2 提供了一个开箱即用的跑步者,称为specs2.run(请参阅In the shell):

    scala -cp ... specs2.run com.company.SpecName [argument1 argument2 ...]
    

    所以基本上你只需要知道:

    1. 你的类路径
    2. 您定义的测试的完全限定名称列表

    以下是使用 sbt 获取它们的方法:

    > print Test/fullClasspath
    * Attributed(/private/tmp/specs-runner/target/scala-2.13/test-classes)
    * Attributed(/private/tmp/specs-runner/target/scala-2.13/classes)
    * Attributed(/Users/eed3si9n/.coursier/cache/v1/https/repo1.maven.org/maven2/org/scala-lang/modules/scala-xml_2.13/1.2.0/scala-xml_2.13-1.2.0.jar)
    ...
    > print Test/definedTests
    * Test foo.HelloWorldSpec : subclass(false, org.specs2.specification.core.SpecificationStructure)
    

    我们可以在 sbt shell 中执行specs2.run runner,如下:

    > Test/runMain specs2.run foo.HelloWorldSpec
    

    跨子项目聚合

    跨子项目聚合测试需要一些思考。我会推荐以下方法,而不是创建一个巨大的组装球。创建一个虚拟子项目testAgg,然后将所有Test/externalDependencyClasspathTest/packageBin收集到它的target/dist中。然后,您可以获取所有 JAR 并根据需要运行 java -jar ...

    如何以编程方式进行?见Getting values from multiple scopes

    lazy val collectJars = taskKey[Seq[File]]("")
    lazy val collectDefinedTests = taskKey[Seq[String]]("")
    lazy val testFilter = ScopeFilter(inAnyProject, inConfigurations(Test))
    
    lazy val testAgg = (project in file("testAgg"))
      .settings(
        name := "testAgg",
        publish / skip := true,
        collectJars := {
          val cps = externalDependencyClasspath.all(testFilter).value.flatten.distinct
          val pkgs = packageBin.all(testFilter).value
          cps.map(_.data) ++ pkgs
        },
        collectDefinedTests := {
          val dts = definedTests.all(testFilter).value.flatten
          dts.map(_.name)
        },
        Test / test := {
          val jars = collectJars.value
          val tests = collectDefinedTests.value
          sys.process.Process(s"""java -cp ${jars.mkString(":")} specs2.run ${tests.mkString(" ")}""").!
        }
      )
    

    运行如下:

    > testAgg/test
    [info] HelloWorldSpec
    [info]
    [info] The 'Hello world' string should
    [info]   + contain 11 characters
    [info]   + start with 'Hello'
    [info]   + end with 'world'
    [info]
    [info]
    [info] Total for specification HelloWorldSpec
    [info] Finished in 124 ms
    3 examples, 0 failure, 0 error
    [info] testAgg / Test / test 1s
    

    如果你真的想要,你可能可以从 collectDefinedTests 生成源代码,使 testAgg 依赖于所有子项目的 Test 配置,并尝试制作一个巨大的组装球,但我将作为一个给读者练习:)

    【讨论】:

    • 感谢您提供的信息丰富的答案,一定会对此进行调查。基本上我们的用例是我们不想运行 SBT 来运行我们所有的测试,你认为官方以更好的方式支持它有意义吗?我还将尝试制作一个 SBT 插件以某种方式自动执行此操作。
    • 是的。我会说这将是一个有趣的插件创意,尤其是如果您可以使其适用于所有测试框架,希望这不会太困难,因为 testOnly 存在。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-02-28
    • 1970-01-01
    • 1970-01-01
    • 2013-02-04
    • 2015-03-05
    • 1970-01-01
    • 2023-01-22
    相关资源
    最近更新 更多