2013-11-21 11 views
14

Esiste un modo per scrivere qualcosa come un "unit test" che si assicura che il codice non sia non compilare?Come si scrive un test di scala unità che garantisce l'interruzione della complicazione?

Perché dovrei volere una cosa del genere? Due ragioni.

1) Controllare la sicurezza del tipo della mia API. Mi piacerebbe un modo per essere sicuro che se qualcuno passa in cattivo valore, si ottiene un errore del compilatore, non solo un errore di runtime. Ovviamente, posso solo eseguire il compilatore e controllare l'errore, ma averlo formalizzato in un test di unità è buono per evitare una regressione & anche per la documentazione.

Ad esempio, considerare questo test. V'è un certo codice commentato che avevo utilizzato per controllare tipo di sicurezza: https://github.com/squito/boxwood/blob/master/core/src/test/scala/com/quantifind/boxwood/EnumUnionTest.scala#L42 (linee 42 & 48 - sulla linea 34 che chiamo un'API diversa che ha un'eccezione runtime, che posso controllare)

Si in realtà mi ci è voluto un po 'per ottenere la sicurezza del tipo giusto, quindi quelli erano controlli importanti. Ora se vado a modificare l'implementazione sottostante, non posso semplicemente eseguire la mia suite di test - Devo anche ricordare di decommentare quelle righe e verificare un errore del compilatore.

2) Verifica gestione errori di macro. Se una macro ha qualche input negativo, dovrebbe dare luogo a un errore del compilatore. Stessi problemi qui, lo stesso desiderio di averlo in una suite di test facile da eseguire.

Uso ScalaTest, ma sono felice di avere una soluzione con qualsiasi framework di test delle unità.

+0

La soluzione ovvia cioè per scrivere un test di unità che esegue il compilatore su una risorsa che si specifica che unit test e per analizzare l'uscita della compilazione. Visualizzandolo in questo modo il codice sorgente non è il test dell'unità stesso, ma solo una risorsa per eseguire un test unitario. – SpaceTrucker

+0

possibile duplicato di [Verifica di un'affermazione secondo cui qualcosa non deve essere compilato] (http://stackoverflow.com/questions/15125457/testing-an-assertion-that-qualcosa-molto-noncompile) –

+0

Vedi [la mia domanda precedente] (http://stackoverflow.com/q/15125457/334519), [risposta di Miles Sabin] (http://stackoverflow.com/a/15132961/334519) e la nuova macro ['illTyped' in Shapeless 2.0] (https://github.com/milessabin/shapeless/blob/49ef0311cadb648653c3749ae057127fe1f265d6/core/src/main/scala/shapeless/test/typechecking.scala). –

risposta

10

Come noto in un commento sopra, Shapeless 2.0 (non ancora rilasciato ma attualmente disponibile come pietra miliare) ha un'implementazione molto bella della funzionalità che stai cercando, basata su una soluzione di Stefan Zeiger. Ho aggiunto una demo al tuo progetto here (nota che ho dovuto effettuare l'aggiornamento a Scala 2.10, poiché questa soluzione utilizza una macro). Funziona in questo modo:

import shapeless.test.illTyped 

//this version won't even compile 
illTyped("getIdx(C.Ooga)") 

//We can have multiple enum unions exist side by side 
import Union_B_C._ 
B.values().foreach {b => Union_B_C.getIdx(b) should be (b.ordinal())} 
C.values().foreach {c => Union_B_C.getIdx(c) should be (c.ordinal() + 2)} 

//Though A exists in some union type, Union_B_C still doesn't know about it, 
// so this won't compile 
illTyped(""" 
    A.values().foreach {a => Union_B_C.getIdx(a) should be (a.ordinal())} 
""") 

Se dovessimo cambiare il codice in seconda convocazione per illTyped a qualcosa che compilerà:

B.values().foreach {a => Union_B_C.getIdx(a) should be (a.ordinal())} 

ci piacerebbe avere il seguente errore di compilazione:

[error] .../EnumUnionTest.scala:56: Type-checking succeeded unexpectedly. 
[error] Expected some error. 
[error]  illTyped(""" 
[error]   ^
[error] one error found 
[error] (core/test:compile) Compilation failed 

Se preferisci un test fallito, puoi facilmente adattare the implementation in Shapeless. Vedere Miles's answer a my previous question per alcune discussioni aggiuntive.

+0

Grazie Travis, mi dispiace per la domanda duplicata. Sei andato oltre e oltre modificando il mio codice per me! Inoltre, vedi la mia risposta qui sotto, a proposito di problemi nell'usarlo nel repl, curioso se hai qualche idea al riguardo. –

0

La risposta di Travis Brown è assolutamente corretta. Proprio nell'interesse della completezza, voglio aggiungere che questo funziona anche per le macro di test, come dimostrato here.

Un piccolo problema: il controllo illTyped non sembra funzionare nel repl. Non è mai genera un errore, anche se l'espressione data fa tipo di controllo. Ma non lasciatevi ingannare, funziona bene.

> test:console 
Welcome to Scala version 2.10.2 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_65). 
Type in expressions to have them evaluated. 
Type :help for more information. 

scala> def foo(s: String) = s 
foo: (s: String)String 

scala> import shapeless.test.illTyped 
import shapeless.test.illTyped 

scala> foo(1) 
<console>:10: error: type mismatch; 
found : Int(1) 
required: String 
       foo(1) 
       ^

scala> illTyped("""foo(1)""") 

scala> illTyped("""foo("hi there")""") // <--- works, but shouldn't! 
+0

Il problema è che in REPL il contesto in cui viene eseguito il check-in del codice non è necessariamente quello che ci si aspetterebbe. 'illTyped (" 2 + 2 ")' fallirà come previsto, così come il codice well-typed che usa qualsiasi classe disponibile definita al di fuori del REPL. Il contesto macro non vede però la definizione di 'foo', quindi considera' foo ("hi there") 'mal digitato e lo lascia passare. Ammetto che questo è confuso, ma è interamente un problema REPL. –

5

Scalatest può anche fare questo.

Checking that a snippet of code does not compile

Often when creating libraries you may wish to ensure that certain arrangements of code that represent potential “user errors” do not compile, so that your library is more error resistant. ScalaTest Matchers trait includes the following syntax for that purpose:

"val a: String = 1" shouldNot compile 

If you want to ensure that a snippet of code does not compile because of a type error (as opposed to a syntax error), use:

"val a: String = 1" shouldNot typeCheck 

Note that the shouldNot typeCheck syntax will only succeed if the given snippet of code does not compile because of a type error. A syntax error will still result on a thrown TestFailedException .

If you want to state that a snippet of code does compile, you can make that more obvious with:

"val a: Int = 1" should compile 

Although the previous three constructs are implemented with macros that determine at compile time whether the snippet of code represented by the string does or does not compile, errors are reported as test failures at runtime.

Problemi correlati