9

我正在使用 ScalaTest 测试我用 Scala 编写的解析器。解析器一次处理一个文件,它有一个单例对象,如下所示:

class Parser{...}
object Resolver {...}

我写的测试用例有点像这样

   describe("Syntax:") {
    val dir = new File("tests\\syntax");
    val files = dir.listFiles.filter(
                    f => """.*\.chalice$""".r.findFirstIn(f.getName).isDefined);

    for(inputFile <- files) {
      val parser = new Parser();
      val c = Resolver.getClass.getConstructor();
      c.setAccessible(true);
      c.newInstance();

      val iserror = errortest(inputFile)
      val result = invokeparser(parser,inputFile.getAbsolutePath) //local method
      it(inputFile.getName + (if (iserror)" ERR" else " NOERR") ){
      if (!iserror) result should be (ResolverSuccess()) 
        else if(result.isInstanceOf[ResolverError]) assert(true)
      }
    }
  }

现在在每次迭代中,单例对象 Resolver 中先前迭代的副作用不会被清除。

有没有办法指定 scalatest 模块重新初始化单例对象?

更新:使用丹尼尔的建议,我更新了代码,还添加了更多细节。

更新:显然是解析器在做一些可疑的事情。在随后的调用中,它不会丢弃先前的 AST。奇怪的。由于这是题外话,我会挖掘更多,可能会使用一个单独的线程进行讨论,谢谢大家的回答

最终更新:问题出在 Resolver 以外的单例对象上,它位于其他文件中,所以我不知何故错过了它。我能够使用 Daniel Spiewak 的回复来解决这个问题。这是做事的肮脏方式,但考虑到我的情况以及我正在编写测试代码的事实,这也是唯一的事情,这不会进入生产使用。

4

3 回答 3

8

根据语言规范,不,没有办法重新创建单例对象。但是,可以反射性地调用单例的构造函数,它会覆盖MODULE$包含实际单例值的内部字段:

object Test

Test.hashCode    // => e.g. 779942019

val c = Test.getClass.getConstructor()
c.setAccessible(true)
c.newInstance()

Test.hashCode    // => e.g. 1806030550

既然我已经和你分享了这个邪恶的秘密,让我警告你永远要这样做。我会非常非常努力地调整代码,而不是像这样玩鬼鬼祟祟的把戏。但是,如果事情如你所说,而你真的别无选择,这至少是一些事情。

于 2010-08-04T23:08:09.817 回答
4

ScalaTest 有几种方法可以让您在测试之间重新初始化事物。但是,如果不了解更多信息,就很难回答这个特定的问题。主要问题是,重新初始化单例对象需要什么?如果在不实例化新的单例对象的情况下无法重新初始化单例对象,那么您需要确保每个测试都重新加载了单例对象,这需要使用自定义类加载器。不过,我很难相信有人会这样设计东西。你能用更多这样的细节更新你的问题吗?我稍后再看一下,看看额外的细节是否让答案更明显。

ScalaTest 有一个运行路径,可以为每次运行重新加载类,但不是测试路径。所以你必须自己动手。这里真正的问题是有人以一种不易测试的方式设计了它。我会在每个测试中使用 URLClassLoader 加载解析器和解析器。这样你每次测试都会得到一个新的解析器。

您需要将 Parser & Resolver 从类路径和运行路径中移除。将它们放入自己的目录中。然后为指向该目录的每个测试创建一个 URLClassLoader。然后在该类加载器上调用 findClass("Parser") 来获取它。我假设 Parser 指的是 Resolver,在这种情况下,JVM 将返回加载 Parser 的类加载器以获取 Resolver,即您的 URLClassLoader。在 Parser 上执行 newInstance 以获取实例。这应该可以解决您的问题,因为您将为每个测试获得一个新的 Resolver 单例对象。

于 2010-08-04T23:15:13.327 回答
0

没有答案,但我确实有一个简单的示例,说明您可能希望在哪里重置单例对象,以便在多种潜在情况下测试单例构造。考虑一些愚蠢的事情,比如下面的代码。您可能希望编写测试来验证在未正确设置环境时是否引发异常,并编写测试以验证在未正确设置环境时不会发生异常。我知道,我知道每个人都说,“当环境设置不正确时提供默认值。” 但我不想这样做;这会导致问题,因为不会有通知您使用错误的系统。

object RequiredProperties extends Enumeration {
  type RequiredProperties = String

  private def getRequiredEnvProp(propName: String) = {
    sys.env.get(propName) match {
      case None => throw new RuntimeException(s"$propName is required but not found in the environment.")
      case Some(x) => x
    }
  }

  val ENVIRONMENT: String = getRequiredEnvProp("ENVIRONMENT")
}

用法:

Init(RequiredProperties.ENVIRONMENT)

如果我提供了默认值,那么用户永远不会知道它没有被设置并默认为开发环境。或类似的东西。

于 2020-02-07T17:32:47.180 回答