46 votes

Tester une affirmation que quelque chose ne doit pas être compilé

Le problème

Quand je travaille avec les bibliothèques qui prennent en charge au niveau du type de programmation, il m'arrive souvent d'écrire des commentaires comme suit (à partir d' un exemple présenté par Paul snively a à Boucle Étrange 2012):

// But these invalid sequences don't compile:
// isValid(_3 :: _1 :: _5 :: _8 :: _8 :: _2 :: _8 :: _6 :: _5 :: HNil)
// isValid(_3 :: _4 :: _5 :: _8 :: _8 :: _2 :: _8 :: _6 :: HNil)

Ou ce, à partir d' un exemple dans l' Informe référentiel:

/**
 * If we wanted to confirm that the list uniquely contains `Foo` or any
 * subtype of `Foo`, we could first use `unifySubtypes` to upcast any
 * subtypes of `Foo` in the list to `Foo`.
 *
 * The following would not compile, for example:
 */
 //stuff.unifySubtypes[Foo].unique[Foo]

C'est une façon très difficile d'en indiquant un certain effet sur le comportement de ces méthodes, et nous pourrions l'imaginer vouloir faire ces affirmations plus formelle-pour l'unité ou les tests de régression, etc.

Pour donner un exemple concret de quoi cela peut être utile dans le contexte d'une bibliothèque comme Informes, il y a quelques jours, j'ai écrit la suite rapidement une première tentative de réponse à cette question:

import shapeless._

implicit class Uniqueable[L <: HList](l: L) {
  def unique[A](implicit ev: FilterAux[L, A, A :: HNil]) = ev(l).head
}

D'où l'intention est que ce sera de la compilation:

('a' :: 'b :: HNil).unique[Char]

Bien que ce ne sera pas:

('a' :: 'b' :: HNil).unique[Char]

J'ai été surpris de constater que cette mise en œuvre d'un type de niveau unique pour HList n'ont pas de travail, parce que Informes serait heureux de trouver un FilterAux exemple dans le dernier cas. En d'autres termes, les éléments suivants devraient compiler, même si vous auriez probablement s'attendre à ne pas:

implicitly[FilterAux[Char :: Char :: HNil, Char, Char :: HNil]]

Dans ce cas, ce que je voyais était un bugou au moins quelque chose de bug-ish-et il a depuis été corrigé.

Plus généralement, on peut imaginer vouloir vérifier le type d'invariant qui était implicite dans mes attentes sur la façon dont FilterAux devrait travailler avec quelque chose comme un test unitaire-aussi bizarre que cela puisse paraître à parler de test au niveau du type de code comme ceci, avec tous les récents débats sur le mérite relatif des types de vs tests.

Ma question

Le problème est que je ne sais pas du tout le genre de framework de test (pour n'importe quelle plate-forme) qui permet au programmeur d'affirmer que quelque chose ne doit pas compiler.

Une approche que je puisse imaginer pour l' FilterAux cas serait d'utiliser l'ancien implicite-argument-à null par défaut truc:

def assertNoInstanceOf[T](implicit instance: T = null) = assert(instance == null)

Qui permet d'écrire le code suivant dans votre unité de test:

assertNoInstanceOf[FilterAux[Char :: Char :: HNil, Char, Char :: HNil]]

Ce qui suit est un diable de beaucoup plus pratique et expressif, bien que:

assertDoesntCompile(('a' :: 'b' :: HNil).unique[Char])

Je veux que ce. Ma question est de savoir si quelqu'un sait de toute bibliothèque de test ou d'un cadre qui prend en charge quelque chose à distance comme elle-idéalement situé pour la Scala, mais je vais me contenter de rien.

25voto

Miles Sabin Points 13604

Pas un cadre, mais Jorge Ortiz (@JorgeO) mentionné certains utilitaires at-il ajouté à l'tests pour Foursquare Rogue bibliothèque NEScala en 2012 qui soutiennent les tests de non-compilation: vous pouvez trouver des exemples ici. J'ai eu l'intention d'ajouter quelque chose comme cela pour informes pour un bon moment.

Plus récemment, Roland Kuhn (@rolandkuhn) a ajouté un mécanisme similaire, cette fois en utilisant Scala 2.10 de l'exécution de la compilation, les tests pour Akka tapé canaux.

Ce sont les deux tests dynamiques de cours: ils ne parviennent pas à (test) de l'exécution si quelque chose qui ne devrait pas compiler n'. Non les macros peuvent fournir une option statique: c'est à dire. une macro peut accepter un non de l'arbre, le type de le vérifier et de le jeter un type d'erreur si il y arrive). Ce pourrait être quelque chose à expérimenter avec les macro-paradis de la direction générale de l'informe. Mais pas une solution pour 2.10.0 ou plus tôt, évidemment.

Mise à jour

Depuis de répondre à la question, une autre approche, en raison de Stefan Zeiger (@StefanZeiger), a refait surface. Celui-ci est intéressant car, comme le non typée macro fait allusion ci-dessus, c'est un moment de la compilation plutôt qu' (test) moment de l'exécution, cependant, il est également compatible avec Scala 2.10.x. En tant que tel, je pense qu'il est préférable de Roland approche.

J'ai maintenant ajouté des implémentations informes pour 2.9.x à l'aide de Jorge approche, pour la 2.10.x à l'aide de Stefan approche et pour la macro paradis à l'aide de la non typée macro approche. Des exemples de tests correspondants peuvent être trouvés ici pour 2.9.x, ici pour la 2.10.x et ici pour la macro paradis.

Le non typée macro tests sont le plus propre, mais de Stefan 2.10.x compatible approche est une seconde près.

20voto

Bill Venners Points 1361

ScalaTest 2.1.0 a la syntaxe suivante pour les assertions :

 assertTypeError("val s: String = 1")
 

Et pour les joueurs :

 "val s: String = 1" shouldNot compile
 

9voto

rintcius Points 1223

Savez-vous à propos de partest dans le projet Scala? E. g. CompilerTest a la doc suivante:

/** For testing compiler internals directly.
* Each source code string in "sources" will be compiled, and
* the check function will be called with the source code and the
* resulting CompilationUnit. The check implementation should
* test for what it wants to test and fail (via assert or other
* exception) if it is not happy.
*/

Il est en mesure de vérifier, par exemple, de savoir si cette source https://github.com/scala/scala/blob/master/test/files/neg/divergent-implicit.scala résultat de ce https://github.com/scala/scala/blob/master/test/files/neg/divergent-implicit.check

Ce n'est pas un ajustement parfait pour votre question (puisque vous ne précisez pas votre cas de test en termes de affirme), mais peut être une approche et/ou vous donner une longueur d'avance.

6voto

EECOLOR Points 6341

Sur la base des liens fournis par Miles Sabin j'ai pu utiliser la version akka

 import scala.tools.reflect.ToolBox

object TestUtils {

  def eval(code: String, compileOptions: String = "-cp target/classes"): Any = {
    val tb = mkToolbox(compileOptions)
    tb.eval(tb.parse(code))
  }

  def mkToolbox(compileOptions: String = ""): ToolBox[_ <: scala.reflect.api.Universe] = {
    val m = scala.reflect.runtime.currentMirror
    m.mkToolBox(options = compileOptions)
  }
}
 

Puis dans mes tests je l'ai utilisé comme ça

 def result = TestUtils.eval(
  """|import ee.ui.events.Event
     |import ee.ui.events.ReadOnlyEvent
     |     
     |val myObj = new {
     |  private val writableEvent = Event[Int]
     |  val event:ReadOnlyEvent[Int] = writableEvent
     |}
     |
     |// will not compile:
     |myObj.event.fire
     |""".stripMargin)

result must throwA[ToolBoxError].like {
  case e => 
    e.getMessage must contain("value fire is not a member of ee.ui.events.ReadOnlyEvent[Int]") 
}
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X