Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/unit-testing/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Unit testing 是否可以测试Haskell I/O函数的返回值?_Unit Testing_Haskell_Functional Programming - Fatal编程技术网

Unit testing 是否可以测试Haskell I/O函数的返回值?

Unit testing 是否可以测试Haskell I/O函数的返回值?,unit-testing,haskell,functional-programming,Unit Testing,Haskell,Functional Programming,Haskell是一种纯函数语言,这意味着Haskell函数没有副作用。I/O是使用表示I/O计算块的单子实现的 是否可以测试Haskell I/O函数的返回值? 假设我们有一个简单的“hello world”程序: main :: IO () main = putStr "Hello world!" 我是否可以创建一个可以运行main的测试线束,并检查I/O monad it是否返回正确的“值”?或者单子应该是不透明的计算块这一事实阻止了我这么做吗 注意,我没有尝试比较I/O操作的返回值我想比

Haskell是一种纯函数语言,这意味着Haskell函数没有副作用。I/O是使用表示I/O计算块的单子实现的

是否可以测试Haskell I/O函数的返回值?

假设我们有一个简单的“hello world”程序:

main :: IO ()
main = putStr "Hello world!"
我是否可以创建一个可以运行
main
的测试线束,并检查I/O monad it是否返回正确的“值”?或者单子应该是不透明的计算块这一事实阻止了我这么做吗

注意,我没有尝试比较I/O操作的返回值我想比较I/O函数的返回值—I/O单子本身


因为在Haskell中,I/O是返回的,而不是执行的,所以我希望检查I/O函数返回的I/O计算块,看看它是否正确。我认为这可以让I/O函数以一种在命令式语言中无法实现的方式进行单元测试,而在命令式语言中,I/O是一种副作用。

我很抱歉地告诉您,您不能这样做

unsafePerformIO
基本上让我们来完成这个。但我强烈希望你不要使用它

Foreign.unsafePerformIO :: IO a -> a

:/

在IO单子中,您可以测试IO函数的返回值。在IO monad之外测试返回值是不安全的:这意味着可以这样做,但只会有破坏程序的风险。仅供专家使用

值得注意的是,在您展示的示例中,
main
的值具有类型
IO()
,这意味着“我是一个IO操作,当执行时,执行一些I/O,然后返回类型
()
”的值。类型
()
的发音为“unit”,这种类型的值只有两个:空元组(也写为
()
,发音为“unit”)和“bottom”,这是Haskell对不会终止或出错的计算的称呼

值得指出的是,从IO monad内部测试IO函数的返回值是非常简单和正常的,惯用的方法是使用
do
表示法。

我喜欢一个类似的问题及其注释。基本上,IO通常会产生一些变化,这些变化可能会从外部注意到您的测试需要与该更改是否正确有关(例如,生成了正确的目录结构等)

基本上,这意味着“行为测试”,在复杂的情况下,这可能是一个相当痛苦的问题。这就是为什么您应该将代码中特定于IO的部分保持在最低限度,并将尽可能多的逻辑移到纯(因此非常容易测试)函数的部分原因

同样,您可以使用断言函数:

actual_assert :: String -> Bool -> IO ()
actual_assert _   True  = return ()
actual_assert msg False = error $ "failed assertion: " ++ msg

faux_assert :: String -> Bool -> IO ()
faux_assert _ _ = return ()

assert = if debug_on then actual_assert else faux_assert
(您可能希望在构建脚本之前构建的单独模块中定义
debug_on
。此外,如果不是标准库,Hackage上的软件包很可能会以更精细的形式提供这一点……如果有人知道这样的工具,请编辑此帖子/评论,以便我可以编辑。)

我认为GHC将足够聪明,完全跳过它发现的任何虚假断言,而实际断言肯定会在失败时使您的程序崩溃


在我看来,这不太可能足够——你仍然需要在复杂的场景中进行行为测试——但我想这有助于检查代码所做的基本假设是否正确。

我这样做的方法是创建我自己的IO monad,其中包含我想要建模的动作。我将运行monadic我想在我的monad中比较计算结果,并比较它们的效果

让我们举一个例子。假设我想对打印内容建模。然后我可以像这样对IO monad建模:

data IO a where
  Return  :: a -> IO a
  Bind    :: IO a -> (a -> IO b) -> IO b
  PutChar :: Char -> IO ()

instance Monad IO where
  return a = Return a
  Return a  >>= f = f a
  Bind m k  >>= f = Bind m (k >=> f)
  PutChar c >>= f = Bind (PutChar c) f

putChar c = PutChar c

runIO :: IO a -> (a,String)
runIO (Return a) = (a,"")
runIO (Bind m f) = (b,s1++s2)
  where (a,s1) = runIO m
        (b,s2) = runIO (f a)
runIO (PutChar c) = ((),[c])
以下是我将如何比较效果:

compareIO :: IO a -> IO b -> Bool
compareIO ioA ioB = outA == outB
  where ioA = runIO ioA ioB
有些事情是这种模型无法处理的。例如,输入是很棘手的。但我希望它能适合您的使用情况。我还应该提到,有更聪明、更有效的方法可以用这种方式对效果进行建模。我选择这种方法是因为我认为它是最容易理解的方法


关于更多信息,我可以推荐论文《野兽之美:尴尬团队的功能语义学》你可以用它来测试一些一元代码。我已经很久没有读过这篇文章了,所以我不记得它是否适用于IO操作或者它可以应用于什么类型的一元计算。另外,你可能发现很难将你的单元测试表示为QuickCheck属性不过,作为一个非常满意的QuickCheck用户,我会说它比什么都不做或使用
unsafePerformIO

好得多。为什么不呢:
test=main>=\d->return$\u test\u value\ud
?因此,在我的测试代码中不能使用unsafePerformIO,但在我的生产代码中不能使用unsafePerformIO来编程测试IO?@ctford:我真正建议的是让您的测试代码在IO monad中工作。monad不一定是“不透明”的计算块。例如,列表和可能的monad都有应用程序可见的行为-IO monad是唯一一个(我知道的)专门设计用于将程序逻辑与其某些行为隔离开来。Control.Monad.ST也相当不透明。我对计算理论方面的东西不太在行。但这不等于停止问题吗?我的意思是,你会有一个返回程序的函数(IO操作)我想写另一个程序来静态分析它的正确性。这是描述问题的有效方法吗?这是可判定的吗?我希望测试IO操作本身是正确的,而不是操作返回的结果(正如你正确指出的,在这种情况下,它什么都不是)。我希望区分