Approaches to testing that a method is not available on a type
Given a type hierarchy for a game which strongly distinguishes whose turn is next:
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]
}
Can I make the following, important assertion without resorting to reflection?
"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.
}
}
EDIT: My attempt to implement @Martin's solution doesn't work. Any thoughts on what's wrong here? From the 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("")
开发者_JS百科 ^
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
should have been true, because b
should not match on the structural type of { def c(s: String) }
You don't test what the type system already guarantees. In fact, the type system is already a test of certain properties of your program.
You could further on to test that the types you have guarantee a certain property (like no player making a move twice in a row), but this kind of thing is restricted to languages like Agda and Coq, for now.
Assuming BlackPiece is not a subtype of WhitePiece:
WhiteToPlayInstance.move(BlackPiece, s) should not compile - which means you can't write a test for it. The type system ensures that you can't move a BlackPiece on a WhiteToPlay.
EDIT: As Thomas pointed out, the below answer is nonsense since structural types cannot be used in pattern matches in the JVM version of scala.
Under normal cicumstances this doesnt make much sense because scala is statically typed and things like that are taken care of by the compiler but if you do make massive use of reflection or structural typing in your code it might be a good test:
instance match {
case x: { def move(p: BlackPiece, s: Square): Either[FinishedGame, WhiteToPlay] } => // error
case _ => // no error
}
If you really want to test for such things, move the check from type checking to something dynamic. Assume that WhitePiece
and BlackPiece
share a common supertype Piece
:
trait Game {
def move(p : Piece, s : Square) : Either[FinishedGame, WhiteToPlay]
}
trait BlackToPlay extends Game
trait WhiteToPlay extends Game
Then a test could look like this:
val b2p : BlackToPlay = ...
val bp : BlackPiece = ...
val wp : WhitePiece = ...
{a move bp} must not produce [IllegalMoveException]
{a move wp} must produce [IllegalMoveException]
I am not sure this would be good design, but it makes your system explicitly testable.
I know you didn't want a reflection solution, but you could (if scala 2.9 is acceptable) use the new Dynamic trait like this:
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: _*)
}
}
... and that will give this positive test:
val dynamicWhiteToPlay = new ReflectionDynamic(whiteToPlay)
dynamicWhiteToPlay.move(new WhitePiece, new Square) must_== Right(blackToPlay)
... and this for negative:
dynamicWhiteToPlay.move(new BlackPiece, new Square) must throwA[NoSuchMethodException]
The question is akin to asking: Given val f: (Boolean) => Int
, how can I test that f("hello world")
is rejected by the compiler?
After some brief conversation at the Melbourne Scala User Group my question was validated (yay). After all, the restriction I'm trying to test for is included by design and therefore deserves a test.
Bernie Pope suggested that the mechanism required is Automated Theorem Proving. @daniel-c-sobral was kind enough to mention Agda and Coq in a slightly different context and indeed these are ATP technologies which could prove my application to be correct.
Another suggestion was to execute the offending code as a script and assert that it fails. A poor-mans eval
, if you like.
精彩评论