Cli*_*ler 37 testing haskell mocking
假设我正在定义一个Haskell函数f(纯函数或动作),并且在f I函数g中调用函数g.例如:
f = ...
g someParms
...
Run Code Online (Sandbox Code Playgroud)
如何用模拟版本替换函数g进行单元测试?
如果我在Java中工作,g将是SomeServiceImpl实现接口的类的方法SomeService.然后,我使用依赖注入告诉f要么使用SomeServiceImpl或MockSomeServiceImpl.我不知道如何在Haskell中做到这一点.
是介绍类型SomeService的最佳方法:
class SomeService a where
g :: a -> typeOfSomeParms -> gReturnType
data SomeServiceImpl = SomeServiceImpl
data MockSomeServiceImpl = MockSomeServiceImpl
instance SomeService SomeServiceImpl where
g _ someParms = ... -- real implementation of g
instance SomeService MockSomeServiceImpl where
g _ someParms = ... -- mock implementation of g
Run Code Online (Sandbox Code Playgroud)
然后,重新定义f如下:
f someService ... = ...
g someService someParms
...
Run Code Online (Sandbox Code Playgroud)
看起来这样可行,但我只是在学习Haskell并想知道这是否是最好的方法呢?更一般地说,我喜欢依赖注入的想法,不仅仅是为了模拟,而且还使代码更具可定制性和可重用性.通常,我喜欢不被锁定到一段代码使用的任何服务的单个实现中的想法.在代码中广泛使用上述技巧以获得依赖注入的好处是否被认为是一个好主意?
编辑:
让我们更进一步.假设我在一个模块中有一系列函数a,b,c,d,e和f,它们都需要能够从不同的模块引用函数g,h,i和j.并且假设我想能够模拟函数g,h,i和j.我可以清楚地将4个函数作为参数传递给af,但是将4个参数添加到所有函数中会有点痛苦.另外,如果我需要更改任何af的实现以调用另一种方法,我需要更改其签名,这可能会产生令人讨厌的重构练习.
使这种情况变得容易的任何技巧?例如,在Java中,我可以使用其所有外部服务构造一个对象.构造函数会将服务存储在成员变量中.然后,任何方法都可以通过成员变量访问这些服务.因此,随着方法被添加到服务中,方法签名都不会改变.如果需要新服务,则只会更改构造函数方法签名.
Apo*_*isp 22
当您可以进行基于规格的自动化测试时,单元测试适用于笨蛋.您可以使用任意生成(模拟)功能Arbitrary提供类型级快速检查(你正在寻找的概念是coarbitrary),并具有快速检查,只要你喜欢用尽可能多的"模拟"功能测试功能.
"依赖注入"是隐式参数传递的简并形式.在Haskell中,您可以使用Reader,或者Free以更少的大惊小怪来实现相同的功能.
eph*_*ent 16
另一种选择:
{-# LANGUAGE FlexibleContexts, RankNTypes #-}
import Control.Monad.RWS
data (Monad m) => ServiceImplementation m = ServiceImplementation
{ serviceHello :: m ()
, serviceGetLine :: m String
, servicePutLine :: String -> m ()
}
serviceHelloBase :: (Monad m) => ServiceImplementation m -> m ()
serviceHelloBase impl = do
name <- serviceGetLine impl
servicePutLine impl $ "Hello, " ++ name
realImpl :: ServiceImplementation IO
realImpl = ServiceImplementation
{ serviceHello = serviceHelloBase realImpl
, serviceGetLine = getLine
, servicePutLine = putStrLn
}
mockImpl :: (Monad m, MonadReader String m, MonadWriter String m) =>
ServiceImplementation m
mockImpl = ServiceImplementation
{ serviceHello = serviceHelloBase mockImpl
, serviceGetLine = ask
, servicePutLine = tell
}
main = serviceHello realImpl
test = case runRWS (serviceHello mockImpl) "Dave" () of
(_, _, "Hello, Dave") -> True; _ -> False
Run Code Online (Sandbox Code Playgroud)
这实际上是在Haskell中创建OO样式代码的众多方法之一.
要跟进编辑询问多个函数,一个选项是将它们放入记录类型并传入记录.然后您可以通过更新记录类型来添加新的.例如:
data FunctionGroup t = FunctionGroup { g :: Int -> Int, h :: t -> Int }
a grp ... = ... g grp someThing ... h grp someThingElse ...
Run Code Online (Sandbox Code Playgroud)
在某些情况下可能可行的另一种选择是使用类型类.例如:
class HasFunctionGroup t where
g :: Int -> t
h :: t -> Int
a :: HasFunctionGroup t => <some type involving t>
a ... = ... g someThing ... h someThingElse
Run Code Online (Sandbox Code Playgroud)
这只有在你能找到函数共有的类型(或多种类型,如果你使用多参数类型类)时才有效,但是在适当的情况下它会给你很好的惯用Haskell.
| 归档时间: |
|
| 查看次数: |
4821 次 |
| 最近记录: |