【问题标题】:Approaches to testing that a method is not available on a type测试方法在类型上不可用的方法
【发布时间】:2011-07-13 22:47:48
【问题描述】:

给定一个游戏的类型层次结构,它强烈区分下一个轮到谁:

trait Game
trait BlackToPlay extends Game {
  def move(p: BlackPiece, s: Square): Either[FinishedGame, WhiteToPlay]
}
trait WhiteToPlay extends Game {
  def move(p: WhitePiece, s: Square): Either[FinishedGame, BlackToPlay]
}

我可以在不借助反思的情况下做出以下重要的断言吗?

"A game with white to play" should {
  "not allow black to play" in {
    // an instance of whiteToPlay should not 
    // have the `move(BlackPiece, Square)` method.
  }
}

编辑:我尝试实施@Martin 的解决方案不起作用。对这里有什么问题有任何想法吗?来自 REPL:

scala> class B() {
     |   def b(s: String) = s
     | }
defined class B

scala> val b = new B()
b: B = B@420e44

scala> b.c("")
<console>:8: error: value c is not a member of B
       b.c("")
         ^

scala> b match {
     |   case _: { def c(s: String) } => false
     |   case _ => true
     | }
warning: there were unchecked warnings; re-run with -unchecked for details
res7: Boolean = false

res7 应该是 true,因为 b 不应该匹配 { def c(s: String) } 的结构类型

【问题讨论】:

  • 阅读警告,然后使用-unchecked 重新运行。您将看到一个警告,大意是结构类型擦除到AnyRef。或者,换句话说,它总是被接受。
  • 或者甚至输入:power然后settings.unchecked.value = true来激活-unchecked

标签: testing scala specs


【解决方案1】:

这个问题类似于问:给定val f: (Boolean) =&gt; Int,我如何测试f("hello world") 是否被编译器拒绝?

Melbourne Scala User Group 进行了一些简短的交谈后,我的问题得到了验证(耶)。毕竟,我试图测试的限制是设计包含的,因此值得测试。

Bernie Pope 建议所需的机制是Automated Theorem Proving。 @daniel-c-sobral 非常友好地在稍微不同的背景下提到了 Agda 和 Coq,这些确实是 ATP 技术,可以证明我的应用程序是正确的。

另一个建议是将有问题的代码作为脚本执行并断言它失败。一个穷人eval,如果你愿意的话。

【讨论】:

    【解决方案2】:

    我知道您不想要反射解决方案,但您可以(如果可以接受 scala 2.9)像这样使用新的 Dynamic trait:

    class ReflectionDynamic[T <: AnyRef](t: T) extends Dynamic {
      def typed[A]: A = sys.error("doh");
    
      def applyDynamic(name: String)(args: Any*) = {
        val argRefs = args.map {
          case a: AnyRef => a
          case _ => sys.error("only AnyRefs")
        }
        t.getClass.getMethod(name, argRefs.map(_.getClass): _*).invoke(t, argRefs: _*)
      }
    }
    

    ...这将给出积极的测试:

    val dynamicWhiteToPlay = new ReflectionDynamic(whiteToPlay)
    dynamicWhiteToPlay.move(new WhitePiece, new Square) must_== Right(blackToPlay)
    

    ...这表示否定:

    dynamicWhiteToPlay.move(new BlackPiece, new Square) must throwA[NoSuchMethodException]
    

    【讨论】:

      【解决方案3】:

      如果您真的想测试这些东西,请将检查从类型检查转移到动态检查。假设WhitePieceBlackPiece 共享一个共同的超类型Piece

      trait Game {
        def move(p : Piece, s : Square) : Either[FinishedGame, WhiteToPlay]
      }
      
      trait BlackToPlay extends Game
      trait WhiteToPlay extends Game
      

      那么测试可能如下所示:

      val b2p : BlackToPlay = ...
      val bp : BlackPiece = ...
      val wp : WhitePiece = ...
      {a move bp} must not produce [IllegalMoveException]
      {a move wp} must produce [IllegalMoveException]
      

      我不确定这是否是一个好的设计,但它可以让您的系统明确可测试。

      【讨论】:

        【解决方案4】:

        编辑: 正如 Thomas 指出的那样,下面的答案是无稽之谈,因为结构类型不能用于 JVM 版本的 scala 的模式匹配中。


        在正常情况下,这没有多大意义,因为 scala 是静态类型的,编译器会处理类似的事情,但如果你确实在代码中大量使用反射或结构类型,它可能是一个很好的测试:

        instance match {
          case x: { def move(p: BlackPiece, s: Square): Either[FinishedGame, WhiteToPlay] } => // error
          case _ => // no error
        }
        

        【讨论】:

        • 我在洗碗的时候就想到了使用结构化类型。很高兴你接受了它。我不同意你的最后陈述的想法。结构类型检查比 instanceof 检查更安全,因为只有它满足所需的断言。
        • @Synesso 好吧...鉴于上面的代码,我会说它并不安全。因为你不需要测试。您实际上可以通过假设编译器行为正确来证明它;)。但是如果你想测试编译器,你可以这样做。也许如果您有许多特征都具有相同名称和签名的方法,那么使用结构类型进行测试而不是检查所有特征会更容易。
        • 编译器检查被调用的方法是否存在,但不是相反。作为一项学术练习,我想做的是防止由于在类型上添加不需要的方法而导致回归错误。
        • @Synesso 好的...不知道您在代码中做了什么。我现在可以想到如果您在代码中使用反射或结构类型会有意义的情况,所以我编辑了我的答案。
        • 我向马丁道歉。可能我正在尝试做的事情很愚蠢。无论哪种方式,我都不相信。谢谢您的帮助!顺便说一句,我发现您的解决方案匹配不正确。即将编辑我的问题以进行演示。
        【解决方案5】:

        您不测试类型系统已经保证的内容。事实上,类型系统已经是对程序某些属性的测试。

        您可以进一步测试您所拥有的类型是否保证了某种属性(例如没有玩家连续两次移动),但这种事情目前仅限于像 Agda 和 Coq 这样的语言。

        【讨论】:

        • 谢谢丹尼尔。是的,我经常争辩说,类型系统提供了我们所能拥有的最便宜的“单元测试”。但是,测试用例(部分)被用作防止回归的保护。类型系统不会断言方法不存在。我想要一个测试以确保层次结构中对等类型的方法永远不会向上提升。例如,如果 BlackToPlay.move 被提升为 Game 类型,那么 WhiteToPlay 将被破坏。这个好像不行。
        【解决方案6】:

        假设 BlackPiece 不是 WhitePiece 的子类型:

        WhiteToPlayInstance.move(BlackPiece, s) 不应编译 - 这意味着您无法为其编写测试。类型系统确保您无法在 WhiteToPlay 上移动 BlackPiece。

        【讨论】:

          猜你喜欢
          • 2020-12-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-11-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多