如何在Scala.js测试套件之间共享数据?
Scala.js测试套件间共享数据的解决方案
问题背景
在ScalaTest中,通过公共单例对象SharedTestData在测试套件间共享数据,JVM环境下可正常工作,但Scala.js环境中完全无效——因为Scala.js的每个测试套件会启动全新的程序执行实例,单例对象的状态无法跨实例共享。
示例代码:
import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers object SharedTestData { var useCount = 0 def useMe(): Int = { useCount += 1 useCount } } class ATest extends AnyFlatSpec with Matchers { "This" should "pass" in { val used = SharedTestData.useMe() info(s"Used $used times") } } class BTest extends AnyFlatSpec with Matchers { "This" should "pass as well" in { val used = SharedTestData.useMe() info(s"Used $used times") } }
执行sbt test后的输出:
[info] BTest: [info] This [info] - should pass as well [info] + Used 1 times [info] ATest: [info] This [info] - should pass [info] + Used 1 times [info] Run completed in 422 milliseconds. [info] Total number of tests run: 2
当需要共享的是耗时数分钟的计算结果时,重复计算会极大降低测试效率,因此需要可靠的跨套件数据共享方案。
可行解决方案
1. 利用运行环境的持久化存储(浏览器/Node.js)
Scala.js测试运行在浏览器或Node.js环境中,可借助这些环境的持久化机制实现跨套件状态共享:
浏览器环境(使用localStorage)
改造SharedTestData,将状态存储在浏览器的localStorage中:
import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers import scala.scalajs.js object SharedTestData { private val storageKey = "shared_test_use_count" private def currentCount: Int = { js.Dynamic.global.localStorage.getItem(storageKey).asInstanceOf[js.UndefOr[String]] .map(_.toInt) .getOrElse(0) } def useMe(): Int = { val newCount = currentCount + 1 js.Dynamic.global.localStorage.setItem(storageKey, newCount.toString) newCount } // 测试前重置状态,避免影响后续测试运行 def reset(): Unit = { js.Dynamic.global.localStorage.removeItem(storageKey) } } // 在测试类中添加初始化/清理逻辑 class ATest extends AnyFlatSpec with Matchers { override def beforeAll(): Unit = { // 仅在第一个运行的套件中重置,可根据需求调整 if (SharedTestData.currentCount == 0) { SharedTestData.reset() } } "This" should "pass" in { val used = SharedTestData.useMe() info(s"Used $used times") } } class BTest extends AnyFlatSpec with Matchers { "This" should "pass as well" in { val used = SharedTestData.useMe() info(s"Used $used times") } }
Node.js环境(使用文件系统)
在Node.js环境下,用文件系统存储共享数据:
import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers import scala.scalajs.js import scala.scalajs.js.annotation.JSImport // 引入Node.js的fs模块 @JSImport("fs", JSImport.Namespace) @js.native object FS extends js.Object { def readFileSync(path: String, encoding: String): String = js.native def writeFileSync(path: String, data: String): Unit = js.native def existsSync(path: String): Boolean = js.native } object SharedTestData { private val dataFile = "./test-shared-data.json" private def currentCount: Int = { if (FS.existsSync(dataFile)) { val content = FS.readFileSync(dataFile, "utf-8") js.JSON.parse(content).asInstanceOf[js.Dynamic].count.asInstanceOf[Int] } else 0 } def useMe(): Int = { val newCount = currentCount + 1 FS.writeFileSync(dataFile, js.JSON.stringify(js.Dynamic.literal(count = newCount))) newCount } def reset(): Unit = { if (FS.existsSync(dataFile)) { val unlink = js.Dynamic.global.fs.unlinkSync.asInstanceOf[String => Unit] unlink(dataFile) } } } // 测试类中添加重置逻辑 class ATest extends AnyFlatSpec with Matchers { override def beforeAll(): Unit = { SharedTestData.reset() } "This" should "pass" in { val used = SharedTestData.useMe() info(s"Used $used times") } }
2. 合并相关测试到同一个套件
如果测试逻辑允许,将需要共享数据的测试用例合并到同一个AnyFlatSpec类中,所有测试用例会在同一个Scala.js执行实例中运行,单例对象状态可正常共享:
import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers object SharedTestData { var useCount = 0 def useMe(): Int = { useCount += 1 useCount } } class SharedDataSpec extends AnyFlatSpec with Matchers { "Test A" should "pass" in { val used = SharedTestData.useMe() info(s"Used $used times") } "Test B" should "pass as well" in { val used = SharedTestData.useMe() info(s"Used $used times") } }
3. 预计算共享数据并作为测试资源
对于耗时的计算,可在测试执行前预先计算好数据,保存为静态资源文件(如JSON、二进制文件),所有测试套件直接读取该文件即可。这种方式适合数据不随测试变化的场景:
- 在
sbt中添加预计算任务,在test任务前执行,生成共享数据文件 - 测试套件中读取该文件加载数据,无需重复计算
内容的提问来源于stack exchange,提问作者Suma
相关产品推荐
相关产品推荐

