3

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

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应该是真的,因为b在结构类型上不应该匹配{ def c(s: String) }

4

6 回答 6

5

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

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

于 2011-03-12T14:52:36.220 回答
3

假设 BlackPiece 不是 WhitePiece 的子类型:

WhiteToPlayInstance.move(BlackPiece, s) 不应该编译——这意味着你不能为它编写测试。类型系统确保您不能在 WhiteToPlay 上移动 BlackPiece。

于 2011-03-12T06:43:22.393 回答
1

这个问题类似于问:鉴于val f: (Boolean) => Int,我如何测试f("hello world")被编译器拒绝的内容?

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

Bernie Pope 建议所需的机制是自动定理证明。@daniel-c-sobral 非常友好地在稍微不同的上下文中提到了 Agda 和 Coq,事实上这些是 ATP 技术,可以证明我的应用程序是正确的。

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

于 2011-03-28T23:24:06.170 回答
1

如果您真的想测试这些东西,请将检查从类型检查转移到动态检查。假设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]

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

于 2011-03-13T20:01:06.660 回答
1

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

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]
于 2011-03-15T00:35:47.270 回答
1

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


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

instance match {
  case x: { def move(p: BlackPiece, s: Square): Either[FinishedGame, WhiteToPlay] } => // error
  case _ => // no error
}
于 2011-03-12T07:30:47.050 回答