【问题标题】:Haskell - assert a function was calledHaskell - 断言一个函数被调用
【发布时间】:2014-08-06 16:35:44
【问题描述】:

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

假设我有两个函数 foobar 可以转换我的数据。

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

我有一个函数可以在 Stuff 上应用 foobar,具体取决于它是否收到 'f' 或 'b' 作为它的第二个参数并返回应用函数的结果。

apply :: Stuff -> Char -> Stuff

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

我是否可以验证函数 foo 或 bar 是否被调用?取决于传递给应用的参数是什么?

【问题讨论】:

  • 你不能在foobar 的正文中写一个print 或类似的语句,看看哪个被调用了吗?你加载了哪些 monad?
  • 如果你是纯函数式的,你不应该能够观察到你是否正在执行一个函数,或者执行了多少次(因为调用函数不应该有任何副作用,并且观察到你已经执行了这确实是一个副作用)。虽然我对 ruby​​-rspec 或 hspec 了解不多,所以我不确定,但是在纯函数式语言中,如果你不能区分两个返回值,你实际上是在调用同一个函数
  • 基本上,如果您将元数据添加到返回值以识别已调用的元数据,则您正在执行单子转换:如果您使用 do 语法,haskell 中的单子会透明地执行此类操作.如果您找到答案并且满意,您应该在此处写下并接受它,以供将来参考
  • 如果你只是将它用于调试而不是生产代码,你可以稍微作弊并使用Debug.Trace
  • 您肯定确实想要测试返回值(因为这就是您应该关心的全部)。基本上foo x = apply x 'f' 用于一些合适的x 和bar/y 相同。

标签: haskell hunit hspec


【解决方案1】:

“我正在考虑更多的 TDD,比如在 OOP 语言中。这样的事情在 Haskell 中可能吗?”

更好的问题是“在 Haskell 中这样的事情有必要吗?” ;-)

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

在 OO 语言中,我们构建与其他对象对话以完成工作的对象。为了测试这样一个对象,我们构建了一堆假对象,将真实对象连接到假对象,运行我们想要测试的方法,并断言它调用具有预期输入的假方法,等等。

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

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

使用 Haskell,我们将任何可能影响现实世界的事物与仅进行数据转换的事物区分开来。测试仅在内存中转换数据的东西非常简单! (总的来说,测试与真实世界交互的代码部分仍然很困难。但希望这些部分现在非常小。)

选择的 Haskell 测试风格似乎是基于属性的测试。例如,如果你有一个求解方程的函数,你编写一个 QuickCheck 属性,它随机生成 100 个方程,并且对于每个方程,它检查返回的数字是否真正解决了原始方程。这是一小部分代码,可以自动测试几乎所有你想知道的东西! (但不完全是:您需要确保“随机”选择的方程式实际上测试了您关心的所有代码路径。)

【讨论】:

  • 在与比我更了解 Haskell 和函数式编程的人交谈后,我想了很多 ;-)。感谢您的回复,我打算发布类似的答案,但我想我的措辞不如您。
【解决方案2】:

(不完全是 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 .)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-01-20
    • 1970-01-01
    • 2010-09-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多