ScalaTest:Singleton Object重新初始化的问题

时间:2010-08-04 21:45:09

标签: scala scalatest

我正在使用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)
      }
    }
  }

现在,在每次迭代中,单个对象解析器中先前迭代的副作用都不会被清除。

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

更新:使用Daniel的建议,我更新了代码,还添加了更多详细信息。

更新:显然,Parser正在做一些可疑的事情。在后续调用中,它不会丢弃先前的AST。奇怪。由于这不是主题,我会挖掘更多,并可能使用单独的线程进行讨论,谢谢所有回答

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

3 个答案:

答案 0 :(得分:7)

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

object Test

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

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

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

现在我和你分享了这个邪恶的秘密,让我告诉你永远不要永远这样做。我会非常努力地调整代码,而不是像这样玩怪异的技巧。但是,如果事情就像你说的那样,而你真的没有别的选择,那至少就是这样。

答案 1 :(得分:4)

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

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

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

答案 2 :(得分: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)

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