Ho visto come QuickCheck può essere utilizzato per testare il codice monadico e non monadico, ma come posso usarlo per testare il codice che gestisce gli errori, cioè stampa un messaggio e poi chiama exitWith
?Utilizzo di QuickCheck per testare condizioni di errore intenzionali
risposta
Prima una dichiarazione di non responsabilità: non sono un esperto di QuickCheck e non avevo esperienza con il controllo monadico prima della domanda, ma vedo lo stackoverflow come un'opportunità per imparare cose nuove. Se c'è una risposta da esperti che dice che questo può essere fatto meglio, rimuoverò il mio.
Supponiamo di avere una funzione test
che può generare eccezioni utilizzando exitWith
. Ecco come penso che puoi provarlo. La funzione chiave è protect
, che rileva l'eccezione e la converte in qualcosa su cui è possibile testare.
import System.Exit
import Test.QuickCheck
import Test.QuickCheck.Property
import Test.QuickCheck.Monadic
test :: Int -> IO Int
test n | n > 100 = do exitWith $ ExitFailure 1
| otherwise = do print n
return n
purifyException :: (a -> IO b) -> a -> IO (Maybe b)
purifyException f x = protect (const Nothing) $ return . Just =<< f x
testProp :: Property
testProp = monadicIO $ do
input <- pick arbitrary
result <- run $ purifyException test $ input
assert $ if input <= 100 then result == Just input
else result == Nothing
Ci sono due svantaggi in questo, per quanto posso vedere, ma non ho trovato alcun modo su di loro.
ho trovato alcun modo per estrarre l'eccezione
ExitCode
dalAnException
cheprotect
in grado di gestire. Pertanto, tutti i codici di uscita sono trattati nello stesso modo (sono mappati suNothing
). Mi sarebbe piaciuto avere:purifyException :: (a -> IO b) -> a -> IO (Either a ExitCode)
ho trovato alcun modo per testare il comportamento di I/O di test. Supponiamo
test
era:test :: IO() test = do n <- readLn if n > 100 then exitWith $ ExitFailure 1 else print n
Allora come è possibile provarlo?
Apprezzerei anche le risposte più esperte.
La funzione QuickCheck expectFailure
può essere utilizzata per gestire questo tipo di cose. Prendete questo semplice (e non raccomandato) framework di gestione degli errori:
import System.Exit
import Test.QuickCheck
import Test.QuickCheck.Monadic
handle :: Either a b -> IO b
handle (Left _) = putStrLn "exception!" >> exitWith (ExitFailure 1)
handle (Right x) = return x
e montare un paio di funzioni fittizie:
positive :: Int -> Either String Int
positive x | x > 0 = Right x
| otherwise = Left "not positive"
negative :: Int -> Either String Int
negative x | x < 0 = Right x
| otherwise = Left "not negative"
Ora possiamo testare alcune proprietà della gestione degli errori. In primo luogo, Right
valori non dovrebbero risultare in eccezioni:
prop_returnsHandledProperly (Positive x) = monadicIO $ do
noErr <- run $ handle (positive x)
assert $ noErr == x
-- Main*> quickCheck prop_returnsHandledProperly
-- +++ OK, passed 100 tests.
Lefts
dovrebbe risultato eccezioni. Notare
Giusto, ma questo sembra aspettarsi tutti i tipi di guasti. Possiamo isolare quelli generati dalle chiamate 'exitWith'? Possiamo affermare quale sarà il codice di errore? – nickie
@nickie È possibile aggiungere un po 'di tubature aggiuntive all'errore che si gestisce se si desidera testare le proprietà sui codici di uscita. Cioè avere una funzione 'verifyCode :: O a b -> ExitCode' che' exitWith' chiama, e fare asserzioni sul comportamento di quello. Non so se è possibile andare oltre. – jtobin
'expectFailure' sembra passare se un test fallisce, al contrario se tutti i test falliscono, che è più quello che sto cercando. – Dan