2012-04-05 9 views
7

Tengo un montón de funciones como: method1, method2, method3. Para todos ellos hay HUnit funciones de prueba como: testMethod1, testMethod2, testMethod3.obtener el nombre de la función dentro de él

testMethod1 = TestCase $ 
    assertEqual "testmethod1" ... 

testMethod2 = TestCase $ 
    assertEqual "testmethod2" ... 

testMethod3 = TestCase $ 
    assertEqual "testmethod3" ... 

me gustaría evitar la copia repetida del nombre de la función como prefijo del mensaje de error y llamarlo algo así:

testMethod1 = TestCase $ 
    assertEqual_ ... 

¿Cómo puede lograrse (cualquier truco "mágica" se aprecia)?

Así que en realidad la pregunta es cómo se puede tomar el nombre de la función dentro de su definición?


actualización.

En realidad no es claro a partir de la pregunta original, en la que quiero manejar ese tipo de situación también:

tProcess = TestCase $ do 
    assertEqual "tProcess" testResult $ someTest 
    assertEqual "tProcess" anotherTestResult $ anotherTest 
    assertEqual "tProcess" resultAgain $ testAgain 

Por último quiero escribir algo así:

tProcess = TestCase $ do 
    assertEqual_ testResult $ someTest 
    assertEqual_ anotherTestResult $ anotherTest 
    assertEqual_ resultAgain $ testAgain 
+1

plantilla haskell – pat

+2

Mi vieja pregunta podría ser útil: http://stackoverflow.com/questions/7896928/how-to-get-variable-name-in-haskell –

Respuesta

10

Usted no puede hacer esto directamente (es decir, para que su caso de prueba comience con testMethodN = ...), pero puede usar Template Haskell para obtener esto:

testCase "testMethod1" [| do 
    assertEqual_ a b 
    assertEqual_ c d 
|] 

Esto implica escribir testCase :: String -> Q Exp -> Q [Dec], una función para convertir el nombre del caso de prueba y una expresión entrecomilladas en una lista de declaraciones. Por ejemplo:

{-# LANGUAGE TemplateHaskell #-} 
     
import Data.Char 
import Control.Applicative 
import Control.Monad 
import Language.Haskell.TH 
import Data.Generics 

assertEqual :: (Eq a) => String -> a -> a -> IO() 
assertEqual s a b = when (a /= b) . putStrLn $ "Test " ++ s ++ " failed!" 

assertEqual_ :: (Eq a) => a -> a -> IO() 
assertEqual_ = error "assertEqual_ used outside of testCase" 

testCase :: String -> Q Exp -> Q [Dec] 
testCase name expr = do 
    let lowerName = map toLower name 
    e' <- [| assertEqual lowerName |] 
    pure <$> valD 
        (varP (mkName name)) 
        (normalB (everywhere (mkT (replaceAssertEqual_ e')) <$> expr)) 
        [] 
  where 
    replaceAssertEqual_ e' (VarE n) | n == 'assertEqual_ = e' 
    replaceAssertEqual_ _ e = e 

La idea básica aquí es generar una definición del nombre de pila, y reemplazar todas las apariciones de la variable assertEqual_ en la expresión citada con assertEqual lowerName. Gracias al soporte de Scrap Your Boilerplate de Template Haskell, no es necesario que atraviese todo el AST, solo especifique una transformación para cada nodo Exp.

Tenga en cuenta que assertEqual_ debe ser un identificador vinculado con el tipo correcto, ya que la expresión entrecomillada se selecciona por tipo antes de pasarse a testCase. Además, testCase se debe definir en un módulo separado del que se utiliza, debido a la restricción de etapa de GHC.

+1

Esto significa que la reflexión no es compatible? –

+1

@Riccardo: Permitirle simplemente acceder al nombre de una función desde adentro sería bastante impuro. Incluso en Lisp, un lenguaje conocido por su metaprogramación, la solución es usar una función macro (en este caso, Template Haskell). – ehird

+1

Actualiza mi pregunta. –

1

Las respuestas existentes explican cómo hacer esto con la metaprogramación, pero una manera de evitar el problema es tener pruebas anónimas que toman su nombre como argumento.

Entonces podemos usar un Data.Map a asociarlos con sus nombres (en este caso sólo estoy usando aserciones primas, además de un poco de azúcar sintáctica del paquete map-syntax):

import Data.Map 
import Data.Map.Syntax 
import Test.HUnit 

assertEqual_ x y n = assertEqual n x y 

Right tests = runMap $ do 
    "test1" ## assertEqual_ 1 2 
    "test2" ## assertEqual_ 1 1 
    "test3" ## assertEqual_ 3 2 

Para ejecutar estos, puede doblar la Data.Map usando una función que:

  • Toma el nombre y la afirmación-espera-para-a-nombre como argumentos
  • pasa el nombre a la afirmación en espera de lucro un nombre de
  • Pasa la resultante Assertion a TestCase
  • Ejecuta el TestCase
  • se une a otra acción monádico, utilizando >>

Utilizamos return() como nuestra acción predeterminada monádico:

runTests = foldWithKey go (return()) tests 
    where go name test = (runTestTT (TestCase (test name)) >>) 

Esto da resultados como:

> go 
### Failure: 
test1 
expected: 1 
but got: 2 
Cases: 1 Tried: 1 Errors: 0 Failures: 1 
Cases: 1 Tried: 1 Errors: 0 Failures: 0 
### Failure: 
test3 
expected: 3 
but got: 2 
Cases: 1 Tried: 1 Errors: 0 Failures: 1 
Cuestiones relacionadas