【发布时间】:2017-01-19 11:27:20
【问题描述】:
我正在按照提出的想法使用 monad-transformers 编写一个小型 DSL 这里here。为了 插图我在这里展示一个小子集。
class Monad m => ProjectServiceM m where
-- | Create a new project.
createProject :: Text -- ^ Name of the project
-> m Project
-- | Fetch all the projects.
getProjects :: m [Project]
-- | Delete project.
deleteProject :: Project -> m ()
这个 DSL 的想法是能够编写 API 级别的测试。为此,所有
这些操作createProject、getProjects、deleteProject 将是
通过对 Web 服务的 REST 调用实现。
我还写了一个 DSL 来写期望。下面给出了一个 sn-p:
class (MonadError e m, Monad m) => ExpectationM e m | m -> e where
shouldContain :: (Show a, Eq a) => [a] -> a -> m ()
您可以想象,可以将更多的 DSL 添加到日志记录的组合中,并且 性能指标see the gist linked above。
使用这些 DSL 可以编写一些简单的测试,如下所示:
createProjectCreates :: (ProjectServiceM m, ExpectationM e m) => m ()
createProjectCreates = do
p <- createProject "foobar"
ps <- getProjects
ps `shouldContain` p
两个解释器如下所示:
newtype ProjectServiceREST m a =
ProjectServiceREST {runProjectServiceREST :: m a}
deriving (Functor, Applicative, Monad, MonadIO)
type Error = Text
instance (MonadIO m, MonadError Text m) => ProjectServiceM (ProjectServiceREST m) where
createProject projectName = return $ Project projectName
getProjects = return []
deleteProject p = ProjectServiceREST (throwError "Cannot delete")
newtype ExpectationHspec m a =
ExpectationHspec {runExpectationHspec :: m a}
deriving (Functor, Applicative, Monad, MonadIO)
instance (MonadError Text m, MonadIO m) => ExpectationM Text (ExpectationHspec m) where
shouldContain xs x = if any (==x) xs
then ExpectationHspec $ return ()
else ExpectationHspec $ throwError msg
where msg = T.pack (show xs) <> " does not contain " <> T.pack (show x)
现在运行场景createProjectCreates monad 转换器可以
以不同的方式堆叠。我发现它有意义的一种方法是:
runCreateProjectCreates :: IO (Either Text ())
runCreateProjectCreates = ( runExceptT
. runExpectationHspec
. runProjectServiceREST
) createProjectCreates
需要:
instance ProjectServiceM (ProjectServiceREST (ExpectationM (ExceptT Text IO)))
instance ExpectationM Text (ProjectServiceREST (ExpectationM (ExceptT Text IO)))
问题在于ProjectSeviceM 的实例必须
了解ExpectationM 并为其创建实例,反之亦然。这些
可以使用 StandaloneDeriving 扩展名轻松创建实例,例如:
deriving instance (ExpectationM Text m) => ExpectationM Text (ProjectServiceREST m)
但是,如果可以避免这种情况会很好,因为我正在泄漏一些 DSL 的任一实现的信息。上面的问题可以吗 克服?
【问题讨论】:
标签: haskell testing dsl monad-transformers