Haskell - 断言一个函数被调用

时间:2014-08-06 16:35:45

标签: haskell hunit hspec

是否可以验证Haskell HSpec中是否调用了函数?

假设我有两个函数 foo bar 来转换我的数据。

foo :: Stuff -> Stuff
bar :: Stuff -> Stuff

我有一个功能,在东西上应用 foo bar ,具体取决于它是否收到'f'或'b'为它的第二个参数并返回应用函数的结果。

apply :: Stuff -> Char -> Stuff

在我的测试中,我已经全面测试了每个函数foo和bar,我不想在 apply 中测试效果。

我是否可以验证是否调用了函数foo或bar?取决于传递的参数适用?

2 个答案:

答案 0 :(得分:3)

"我在思考更多TDD,就像在OOP语言中一样。在Haskell中这样的事情是可能的吗?"

一个更好的问题是"在Haskell中必要是什么?" ; - )

[我意识到这不是你实际问过的问题。随意忽略这个答案。]

在OO语言中,我们构建与其他对象交互的对象以完成其工作。为了测试这样的对象,我们构建了一堆假对象,将真实对象挂钩到伪造对象,运行我们想要测试的方法,并声明它使用预期输入调用伪造方法等。 / p>

在Haskell中,我们编写函数。纯函数唯一做的就是获取一些输入,并产生一些输出。所以测试它的方法就是运行事物,为它提供已知输入并检查它是否返回已知输出。它在执行此操作过程中调用的其他功能并不重要;我们所关心的只是答案是否正确。

特别是,我们通常不会在OOP中这样做的原因是调用一些任意方法可能会导致"实际工作"发生 - 读取或写入磁盘文件,打开网络连接,与数据库和其他服务器通信等。如果您只是测试代码的一部分,那么您不希望测试依赖于是否某些数据库正在某个地方的真实网络服务器上运行;你只想测试代码的一小部分。

使用Haskell,我们将任何可能影响真实世界的东西与刚刚进行数据转换的东西分开。测试只是在内存中转换数据的东西是微不足道的! (一般来说,测试与真实世界交互的代码部分仍然很难。但希望这些部分现在非常小。)

Haskell测试样式的选择似乎是基于属性的测试。例如,如果您有一个函数来求解方程式,那么您可以编写一个随机生成100个方程式的QuickCheck属性,对于每个方程式,它会检查返回的数字是否实际上解决了原始方程式。它是一小撮代码,能够自动测试您想知道的所有内容! (但不完全是:你需要确保随机选择的方程实际上测试了你关心的所有代码路径。)

答案 1 :(得分:0)

(不完全是Haskell,但很接近。)

fooP = point . foo
-- testable property: forall s. foo s = runIdenity $ fooP s

barP = point . bar
-- similar testable property

fooAndWitness :: Stuff -> Writer String Stuff
fooAndWitness = fooM >> tell "foo"
-- testable property forall s. (foo s, "foo") = runWriter $ fooAndWitness s

barAndWitness :: Stuff -> Writer String Stuff
barAndWitness = barM >> tell "bar"
-- similar testable property

applyOpen :: Pointed p => (Stuff -> p Stuff) -> (Stuff -> p Stuff) -> Stuff -> Char -> p Stuff
applyOpen onF _   x 'f' = onF x
applyOpen _   onB x 'b' = onB x
applyOpen _   _   x _   = point x
-- semi-testable property (must fix p):
-- forall f b s c. let a = applyOn f b s c in a `elem` [f s, b s, point s]
-- In particular, if we choose p carefully we can be, at least stochastically,
-- sure that either f, b, or neither were called by having p = Const [Int], and running several tests
-- where two random numbers are chosen, `f _ = Const $ [rand1]`, and `b _ = Const $ [rand2]`
-- and verifying we get one of those numbers, which could not have been known when applyOpen was written.

applyM = applyOpen fooM barM
-- similar testable property, although but be loose the "rigged" tests for variable f/b, so
-- some of our correctness may have to follow from the definition.

apply = (runIdentity .) . applyM
-- similar testable property and caveat

Pointed是适合Functor和Applicative的类型类,并为point提供与purereturn相同的语义。这是唯一的法则来自参数:(. point) . fmap = (point .)