SBT多项目测试无法识别非托管JAR的问题求助
问题:SBT多项目测试时无法识别非托管JAR
我有一个SBT多项目结构,运行runMain时能正常识别/lib里的非托管JAR,但运行测试时却不行。我的目标是创建一个无实际实现的stub子项目,将其作为Provided依赖添加到主项目中,编译时用stub,运行和测试时用/lib里的实际JAR(该JAR仅实现了打印随机字符串的功能)。
项目结构
./MainClass ./MainClass/lib/printsomethingexample_2.13-0.1.0-SNAPSHOT.jar ./MainClass/src/test/scala/Main.scala ./MainClass/src/main/scala/Main.scala ./stub/lib/printsomethingexample_2.13-0.1.0-SNAPSHOT.jar ./stub/src/main/scala/test/stub/Example.scala ./build.sbt
关键文件内容
MainClass/src/main/scala/Main.scala
import test.stub.Example object Main extends App { Example.printSomething() }
MainClass/src/test/scala/Main.scala
import org.scalatest.flatspec._ import test.stub.Example class Main extends AnyFlatSpec { "Unmanaged jar" should "work" in { Example.printSomething() } }
stub/src/main/scala/test/stub/Example.scala
package test.stub object Example { def printSomething(): Unit = ??? }
build.sbt
ThisBuild / version := "0.1.0-SNAPSHOT" ThisBuild / scalaVersion := "2.13.10" lazy val root = (project in file(".")) .settings( name := "stubExample" ).aggregate(MainClass, stub) lazy val stub = project .in(file("stub")) .settings( name := "stub" ) lazy val MainClass = project .in(file("MainClass")) .settings( name := "MainClass", libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.14" % Test, ).dependsOn(`stub` % Provided)
运行情况
- 执行
sbt "MainClass/runMain Main"正常,输出如下:
▶ sbt "MainClass/runMain Main" [info] welcome to sbt 1.6.2 (Azul Systems, Inc. Java 1.8.0_312) [info] loading global plugins from /Users/riccardo/.sbt/1.0/plugins [info] loading project definition from /Users/riccardo/Documents/Projects/stubExample/project [info] loading settings for project root from build.sbt ... [info] set current project to stubExample (in build file:/Users/riccardo/Documents/Projects/stubExample/) [info] running Main Printed Something
- 执行
sbt "MainClass/test"报错:
[info] Main: [info] Unmanaged jar [info] - should work *** FAILED *** [info] scala.NotImplementedError: an implementation is missing
补充说明
非托管JAR里的test.stub.Example实现如下:
package test.stub object Example { def printSomething(): Unit = println("Printed Something") }
我原本以为Provided依赖会把stub从运行类路径中排除,从而让JVM优先加载/lib里的实际实现,但测试时却还是用了stub的未实现版本。如果移除dependsOn,测试能正常运行,但没有非托管JAR时项目无法编译——这也是我用stub做编译依赖的原因。
现在想知道为什么测试时无法识别非托管JAR?
解答
问题核心是SBT中Provided作用域对测试类路径的处理逻辑:
Provided依赖仅会排除Runtime作用域,但测试类路径默认包含Compile和Test作用域的依赖,stub作为Provided依赖依然会被加入测试类路径,且因为是本地项目编译产物,类路径优先级比/lib下的非托管JAR更高,导致测试时优先加载了stub的未实现类。- 而
runMain使用的是Runtime作用域,Provided依赖会被排除,所以能正常加载/lib里的JAR。
解决方法
修改MainClass项目配置,让stub仅在编译阶段生效,同时确保测试类路径排除stub,保留非托管JAR:
lazy val stub = project .in(file("stub")) .settings( name := "stub" ) lazy val MainClass = project .in(file("MainClass")) .settings( name := "MainClass", libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.14" % Test, // 仅在编译时引入stub的产物 Compile / unmanagedClasspath += (stub / Compile / packageBin).value, // 测试类路径过滤掉stub的JAR,保留lib下的实际实现 Test / unmanagedClasspath ~= { cp => cp.filterNot(_.data.getName.startsWith("stub_")) } )
原理说明
- 直接将stub的编译产物加入编译类路径,保证无实际JAR时项目可正常编译。
- 在测试类路径中过滤掉stub的产物,确保测试时JVM优先加载
/lib目录下的实际实现JAR。
这样既满足了编译依赖需求,又能让测试和运行阶段使用真实的JAR实现。
内容的提问来源于stack exchange,提问作者Riccardo
相关产品推荐
相关产品推荐

