2012-04-05 14 views
7

Ho un sacco di funzioni come: method1, method2, method3. Per tutti loro ci sono le funzioni di test HUnit come: testMethod1, testMethod2, testMethod3.get nome della funzione al suo interno

testMethod1 = TestCase $ 
    assertEqual "testmethod1" ... 

testMethod2 = TestCase $ 
    assertEqual "testmethod2" ... 

testMethod3 = TestCase $ 
    assertEqual "testmethod3" ... 

Vorrei evitare di copiare ridondante del nome della funzione come prefisso di errore messaggio e chiamarlo qualcosa di simile:

testMethod1 = TestCase $ 
    assertEqual_ ... 

Come può essere raggiunto (qualsiasi trucco "magia" è apprezzato)?

Quindi, in effetti, la domanda è: come si può inserire il nome della funzione all'interno della sua definizione?


Aggiornamento.

In realtà non è chiaro dalla domanda iniziale, che voglio gestire questo tipo di situazione troppo:

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

Infine voglio scrivere qualcosa del genere:

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

modello haskell – pat

+2

La mia vecchia domanda potrebbe essere utile: http://stackoverflow.com/questions/7896928/how-to-get-variable-name-in-haskell –

risposta

10

Non si può fare questo direttamente (vale a dire che il tuo test case inizia con testMethodN = ...), ma è possibile utilizzare Template Haskell per ottenere questo:

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

Ciò comporta la scrittura di testCase :: String -> Q Exp -> Q [Dec], una funzione per trasformare il nome del caso di test e un'espressione quotata in un elenco di dichiarazioni. Per esempio:

{-# 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 

L'idea di base è quella di generare una definizione del nome dato, e sostituire ogni occorrenza della variabile assertEqual_ nell'espressione indicata con assertEqual lowerName. Grazie al supporto di Template Haskell Scrap Your Boilerplate, non è necessario attraversare l'intero AST, basta specificare una trasformazione per ogni nodo Exp.

Si noti che assertEqual_ deve essere un identificatore associato con il tipo corretto, poiché l'espressione citata è digitata prima di essere passata a testCase. Inoltre, testCase deve essere definito in un modulo separato rispetto a quello in cui è utilizzato, a causa della restrizione di fase di GHC.

+1

Ciò significa che la riflessione non è supportata? –

+1

@Riccardo: consentire semplicemente di accedere al nome di una funzione dall'interno sarebbe piuttosto impuro. Anche in Lisp, una lingua nota per la sua metaprogrammazione, la soluzione è usare una macro funzione (in questo caso, Template Haskell). – ehird

+1

Aggiorna la mia domanda. –

1

Le risposte esistenti spiegano come eseguire questa operazione con metaprogrammazione, ma un modo per evitare il problema è di avere test anonimi che prendono il loro nome come argomento.

Possiamo quindi utilizzare un Data.Map per associarli con i loro nomi (in questo caso sto usando solo asserzioni prime, oltre ad alcuni zucchero sintattico dal pacchetto 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 

Per eseguire questi, abbiamo può piegare la Data.Map utilizzando una funzione che:

  • prende il nome e l'affermazione-attesa-per-un-nome come argomenti
  • passa il nome all'asserzione-attesa-per- un nome
  • Passa la risultante Assertion-TestCase
  • Esegue il TestCase
  • si lega ad un'altra azione della monade, utilizzando >>

Usiamo return() come il nostro difetto azione monadica:

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

Questo dà risultati come:

> 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