测试方法在类型上不可用的方法

时间:2011-03-12 06:14:05

标签: testing scala specs

给出游戏的类型层次结构,强烈区分下一个回合:

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 <{1}}

的结构类型不应该匹配

6 个答案:

答案 0 :(得分:5)

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

你可以进一步测试你保证某种属性的类型(比如没有玩家连续两次移动),但是这种东西仅限于像Agda和Coq这样的语言。

答案 1 :(得分:3)

假设BlackPiece不是WhitePiece的子类型:

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

答案 2 :(得分:1)

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


在正常的情况下,这没有多大意义,因为scala是静态类型的,编译器会照顾到类似的东西但是如果你在代码中大量使用反射或结构类型,那么这可能是一个很好的测试:< / p>

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

答案 3 :(得分: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]

我不确定这会是一个好的设计,但它会使你的系统明确可测试。

答案 4 :(得分:1)

我知道你不想要一个反射解决方案,但你可以(如果scala 2.9可以接受)使用这样的新动态特性:

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]

答案 5 :(得分:1)

问题类似于问:给定val f: (Boolean) => Int,如何测试编译器拒绝f("hello world")

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

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

另一个建议是将违规代码作为脚本执行并断言它失败。如果你愿意,可以使用穷人eval