1

语境

我有一些我试图用 HSpec 测试的解释器的单子函数。它们使用以下 monad 堆栈运行:

type AppState = StateT InterpreterState (ExceptT Events IO)
type AppReturn a = Either Events (a, PState)

runApp :: AppState a -> IO (AppReturn a)
runApp f = runExceptT (runStateT f new)

这是一个简单的例子:

mustEvalExpr :: Expr -> S.AppState (S.Value)
mustEvalExpr e = do
    val <- evalExpr e
    case val of
        Just val' -> return val'
        Nothing   -> throw $ S.CannotEval e

问题是 HSpec 有自己的上下文 ( IO ()),所以我必须在两个上下文之间进行翻译。

当前方法

我正在使用 HSpec,我编写了一个转换器函数来runApp从 HSpec 上下文中获取上下文。

-- imports omitted

extract :: S.AppReturn a -> a
extract st = case st of
    Right (a, _) -> a
    Left ev      -> throw ev

run :: (S.AppReturn a -> b) -> S.AppState a -> IO b
run extractor fn = do
    state <- S.runApp fn
    return $ extractor state

所以我的Spec样子是这样的:

spec :: Spec
spec = do
    describe "mustEvalExpr" $ do
        let badExpr = D.VarExpr $ D.Id "doesntExist"
            goodExpr = D.IntExpr 1
            val = S.IntValue 1

        it "should evaluate and return expression if its a Just" $ do
            (run extract $ do
                I.mustEvalExpr goodExpr
                ) >>= (`shouldBe` val)

        it "should throw error if it gets a Nothing" $ do
            (run extract $ do
                I.mustEvalExpr badExpr
                ) `shouldThrow` anyException

问题

这是我能做的最好的吗?我觉得run extract $ do很好,而且我认为当事情复杂时,明确表达是件好事。

但是我想知道是否有一种方法可以与 HSpec 集成,或者是否有不需要自定义代码来解决这个问题的最佳实践?

4

0 回答 0