我正在开发一个用于测试的库,我希望能够在特定范围内重新定义变量。这是受到let
rspec的启发。
scope
我通过不断隐藏一个名为(类型)的变量,使某些事情得以实现Scope
。该Scope
类型具有
- 一种
apply[T](String)(Scope => T)
定义子作用域的方法,并使用该子作用域作为参数立即评估给定的块 - 在此范围内
let
改进变量值的方法Scoped
get
获取Scoped
此范围内变量值的方法
一个有效的简化示例:
object MyTest extends Scopes with FunSuite {
val env = Scoped[String]("production")
val input = Scoped[Int]()
scope("when the input is zero") { scope =>
scope.let(input, 0)
test("the value equals zero") {
expect(scope.get(input) == 0)
}
scope("and the app is running in staging") { scope =>
scope.let(env, "staging")
test("the value still equals zero") {
expect(scope.get(input) == 0)
}
}
}
}
这是可行的,因为该Scopes
特征提供了val scope = Scope.root
,并且只要您scope
每次创建子范围时始终如一地遮蔽变量,测试就可以引用scope
,并且它将基于定义它们的词法范围。
隐式范围
如果我的方法可以接受,那么 API 就不会那么冗长,并且如果我始终将每个嵌套范围写为,那么implicit Scope
这确实有效(在 Scala 3 中):
scope("nested scope") { implicit scope =>
// ...
}
但这在范围定义上更为冗长,尽管它可以在访问或重新定义范围变量时提高简洁性。
添加隐式 via 宏
我希望能够通过宏减少样板代码。本质上,我希望implicit activeScope =>
在每次调用时自动添加上述样板代码scope
。然后用户的代码将如下所示:
scope("nested scope") {
// I can call a method taking (implicit scope: Scope)
}
implicit val rootScope: Scope = ???
我通过在特征中添加一个来进行类型检查Scopes
,然后scope
在传递给它的块的开头使用一个自动引入隐式的宏。宏的代码生成部分如下所示:
def contextCode[T](scope: Expr[Scope], s: Expr[String], block: Expr[T])(using Quotes, Type[T]): Expr[T] = {
'{
${scope}(${s}) { implicit activeScope =>
(${block})
}
}
}
这可以编译,但是它不使用引入的隐式。我怀疑,由于类型检查发生在宏扩展之前,因此隐式解析也是如此(因此我的新隐式被忽略)。
有什么方法可以使用宏来影响Scope
基于词法范围选择哪个值?我愿意使用一些狡猾的技巧,只要它们是可靠的。
在约束方面,外部scope
定义调用都是在对象创建时进行评估的,因此我同意在定义时使用某种可变状态来跟踪“活动范围”。问题是测试块内的代码需要访问相同的范围,并且该代码运行得更晚(可能并行),因此测试主体中的“活动”范围必须基于其词法范围,而不是运行时状态。
哦,我对测试框架本身没有任何控制权。也就是说,我无法(例如)test
在运行测试主体之前修改该函数以执行任何特殊操作。
您不需要宏,但需要上下文函数和一些实用程序(带有
inline
):(斯卡斯蒂)
每次使用嵌套上下文函数(
?=>
)时,given
内部都会遮蔽given
外部,因此您可以安全且可预测地创建给定值的副本,在嵌套中使用它,并且它不会影响外部的范围 - 这听起来就像您正在尝试做的事情。我没有费心使我的
Scoped
例子具有通用性,但是它应该可以让您了解如何given
在嵌套的上下文函数中掩盖 s。