【问题标题】:Sequential test setup/cleanup in sbtsbt 中的顺序测试设置/清理
【发布时间】:2020-09-21 04:00:14
【问题描述】:

我有一个 SBT 项目,其中多个子项目聚合在一个根项目中。我想确保测试套件是按顺序运行的,所以我设置了Global / concurrentRestrictions += Tags.limit(Tags.Test, 1)

不过,我还使用Tests.Setup()/Cleanup() 来为每个子项目的测试套件初始化大量内存资源。我面临的问题是concurrentRestrictions 对测试设置和清理没有影响:当我希望它在每个测试套件之前和之后按顺序运行时,我的所有测试设置和清理逻辑都是并行运行的。

这是一个示例build.sbt

import sbt._
import scala.sys.process._

lazy val testSettings = Seq(
    testOptions ++= Seq(
      Tests.Setup(() => {
        println(s"setup for ${name.value}")
      }),
      Tests.Cleanup(() => {
        println(s"cleanup for ${name.value}")
      })
    )
)

Global / libraryDependencies ++= Seq(
  "org.scalatest" %% "scalatest" % "3.1.0" % "test"
)

def module(path: String) = {
  Project(path, file(path))
    .settings(testSettings)
}

lazy val module1 = module("module1")

lazy val module2 = module("module2")

lazy val module3 = module("module3")

lazy val root = (project in file("."))
  .aggregate(module1, module2, module3)

Global / concurrentRestrictions += Tags.limit(Tags.Test, 1)

以及我为每个子项目使用的虚拟测试:

import org.scalatest.matchers.should.Matchers
import org.scalatest._

class DummyTest extends FlatSpec with Matchers {
  "this test" should "run a test for module 1" in {}
}

运行sbt test 给出以下输出,以并发的变幻莫测为模:

$ sbt test
[info] Loading settings for project global-plugins from plugins.sbt ...
[info] Loading global plugins from /home/jejost/.sbt/1.0/plugins
[info] Loading project definition from /home/jejost/dev/tmp/sbt-yunowork/project
[info] Loading settings for project root from build.sbt ...
[...]
setup for module1
setup for module3
setup for module2
[info] DummyTest:
[info] this test
[info] - should run a test for module 1
[info] DummyTest:
[info] this test
[info] - should run a test for module 3
[info] DummyTest:
[info] this test
[info] - should run a test for module 2
cleanup for module1
cleanup for module3
cleanup for module2
[...]

当我想要的行为是这样的:

$ sbt test
[info] Loading settings for project global-plugins from plugins.sbt ...
[info] Loading global plugins from /home/jejost/.sbt/1.0/plugins
[info] Loading project definition from /home/jejost/dev/tmp/sbt-yunowork/project
[info] Loading settings for project root from build.sbt ...
[...]
setup for module1
[info] DummyTest:
[info] this test
[info] - should run a test for module 1
cleanup for module1
setup for module3
[info] DummyTest:
[info] this test
[info] - should run a test for module 3
cleanup for module3
setup for module2
[info] DummyTest:
[info] this test
[info] - should run a test for module 2
cleanup for module2

[...]

(模块 1、模块 3、模块 2 的顺序对我来说并不重要,只要设置/拆卸在每个子项目之前/之后是顺序的)

是否可以像这样确保顺序测试设置和清理,理想情况下无需定义我自己的自定义任务?

我正在使用 SBT 1.3.4。

编辑:这已被标记为以下问题的重复:How to run subprojects tests (including setup methods) sequentially when testing 这确实是同一个问题,但是,接受的答案不起作用,并且不会改变任务运行顺序!我试图重现另一个问题的设置,看看它是否是 SBT 中的回归,不幸的是,OP 的 SBT 版本太旧,无法在我的机器上成功构建。

【问题讨论】:

  • 不,不是:我不知道这是否是由于 SBT 中的行为变化/回归,但因为测试设置/清理在其自己的单独任务中运行,添加 parallelExecution in Global := false 可确保只有一个子项目的测试设置任务同时运行,但它不会阻止其他子项目设置任务在该项目的测试和清理完成之前运行
  • 在实践中,我没有看到与parallelExecution in Global := false 并行运行的任务,但执行顺序没有变化,添加parallelExecution in ThisBuild := false 时也没有任何变化
  • 恐怕你无法实现这一点,正如Forking tests 中所阐述的那样:SetupCleanup 在一个组被分叉时,实际的测试类加载器无法提供动作。 我知道你没有使用组,但如果你不能用组来做,你可能根本做不到。

标签: scala testing concurrency sbt


【解决方案1】:

命令与任务不同,默认情况下是按顺序执行的,因此请考虑简单地下降到命令级别

addCommandAlias("sequentialtest", ";module1/test;module2/test;module3/test;")

它给出了类似的东西

sbt:hello-world-scala> sequentialtest
setup for module1
[info] DummyTest:
[info] this test
[info] - should run a test for module 1
cleanup for module1
[info] Run completed in 164 milliseconds.
...
setup for module2
[info] DummyTest:
[info] this test
[info] - should run a test for module 2
cleanup for module2
[info] Run completed in 148 milliseconds.
...
setup for module3
[info] DummyTest:
[info] this test
[info] - should run a test for module 3
cleanup for module3
[info] Run completed in 154 milliseconds.
...

处理评论

commands += Command.command("sequentialtest") { state =>
  Project
   .extract(state)
   .structure
   .allProjects
   .iterator
   .map(_.id)
   .filterNot(_ == "root")
   .map(id => s"$id/test;")
   .mkString :: state
}

【讨论】:

  • 我猜这是一个小小的改进 - 有没有办法在不将其专门绑定到子项目名称或存在多少子项目名称的情况下完成这项工作?换句话说,即使我添加“module4”或删除“module2”而无需更改命令别名,是否有任何方法可以使这项工作?
  • 好的,那个编辑非常令人印象深刻 :) 非常感谢,准备试试这个。
猜你喜欢
  • 2021-07-08
  • 2014-03-29
  • 1970-01-01
  • 2015-02-05
  • 1970-01-01
  • 2021-05-25
  • 1970-01-01
  • 1970-01-01
  • 2019-06-09
相关资源
最近更新 更多