如何在Haskell中用用户输入测试小程序

我正在参加软件测试课程。我们可以选择使用哪种语言编写代码和进行测试。我选择Haskell。我知道,也许这不是学习测试的最佳方法(=但是...

单元测试和编码一直很好!

但是我还需要使用嘲笑来获得更高的成绩。

我的问题是我不太了解Haskell(如Monads之类的东西)。

我已经编写并测试了计算器。现在,我想测试我的主体。我的讲师使用Mockito for Java检查程序是否正确。

是否可以测试我的if语句是否正确? 我尝试阅读有关通过Monad测试IO操作的信息,但我不太了解。 也许我应该在尝试解决此问题之前就了解有关Monad的更多信息?

非常感谢您的帮助或阅读建议!

import Calculator

main :: IO ()
main = do
        putStrLn("What should I calcuclate? ex 3*(2+2)  | quit to exit" )
        line <- getLine
        if line /= "quit"
        then do if correctInput line
                then do putStrLn( show $ calculate line) 
                        main
                else do putStrLn( "Wrong input") 
                        main
        else putStrLn("goodbye")
wang_hecheng 回答:如何在Haskell中用用户输入测试小程序

您可以“模拟”(或“伪造”)依赖项,而不必超越IO

您的程序逻辑通过putStrLngetLine与外部世界进行交互。但是,他们对他们有什么了解?实际上,除了它们的类型String -> IO ()IO String之外,什么都没有。

因此我们可以将它们抽象化,将您的程序逻辑转换为函数myProgramLogic :: (String -> IO ()) -> IO String -> IO (),该函数将有效的操作作为参数接收。将依赖项作为函数参数传递是依赖项注入的低热量版本。

现在问题变成了:如何模拟putStrLngetLine。显然,由于我们要进行自动测试,因此模拟不能是交互式的。但是它们也不会是像\_ -> return ()return "foo"这样总是在做同样事情的无聊动作。他们必须具有状态,必须记录与程序逻辑的交互。

标准库中有一个名为Data.IORef的模块,可让您创建和操作IO中的可变引用。其他语言将其称为“只是一个无聊的常规变量”。

这段代码创建了一个包含字符串列表的可变引用,并且还定义了一个pseudoGetLine函数,该函数每次执行时都会提取其中一个字符串:

main :: IO ()
main = do
    inputsRef <- newIORef ["foo","bar","baz"]
    let pseudoGetLine :: IO String
        pseudoGetLine = do
            atomicModifyIORef inputsRef (\inputs ->
                case inputs of
                    i : is -> (is,i) -- the i becomes the return value of pseudoGetLine 
                    [] -> error "fake inputs exhausted")
    sample <- pseudoGetLine
    print sample

您可以看到变化的方向:伪造两个依赖项并将它们传递给逻辑后,可以检查可变引用的状态(使用类似readIORef的函数),以检查它们是否是预期的。

,

即使在Java或C#等面向对象的环境中,我也无法在Main方法上使用Test Doubles('mocks'),因为您可以在入口点不进行依赖注入;它的签名是固定的。

您通常要做的是定义一个带有依赖项的MainImp,然后使用Test Doubles进行测试,而将实际的Main方法保留为Humble Executable。 / p>

生产代码

您可以在Haskell中执行相同的操作。一种简单的方法是按照 danidiaz 的建议进行操作,并将不纯行为作为参数传递给mainImp

mainImp :: Monad m => m String -> (String -> m ()) -> m ()
mainImp getInput displayOutput = do
  displayOutput "What should I calcuclate? ex 3*(2+2)  | quit to exit" 
  line <- getInput
  if line /= "quit"
  then do if correctInput line
          then do displayOutput $ show $ calculate line
                  mainImp getInput displayOutput
          else do displayOutput "Wrong input"
                  mainImp getInput displayOutput
  else displayOutput "goodbye"

请注意,类型声明显式允许任何Monad m。其中包括IO,这意味着您可以像这样编写实际的main动作:

main :: IO ()
main = mainImp getLine putStrLn

但是,在测试中,您可以使用另一个monad。通常,State非常适合此任务。

测试

您可以从适当的导入开始测试模块:

module Main where

import Control.Monad.Trans.State
import Test.HUnit.Base (Test(..),(~:),(~=?),(@?))
import Test.Framework (defaultMain)
import Test.Framework.Providers.HUnit
import Q58750508

main :: IO ()
main = defaultMain $ hUnitTestToTests $ TestList tests

这使用了HUnit,稍后您将看到inline the tests in a list literal

但是,在进行测试之前,我认为定义一个可以保留控制台状态的特定于测试的类型是有意义的:

data Console = Console { outputs :: [String],inputs :: [String] } deriving (Eq,Show)

您还需要一些与getInputdisplayOutput相对应的功能,但必须在State Console单子而不是IO中运行。这是a technique that I've described before

getInput :: State Console String
getInput = do
  console <- get
  let input = head $ inputs console
  put $ console { inputs = tail $ inputs console }
  return input

请注意,此函数不安全,因为它使用headtail。我将其保留为安全性练习。

它使用get检索控制台的当前状态,拉出head的“队列”中的inputs,并在返回input之前更新状态

同样,您可以在displayOutput monad中实现State Console

displayOutput :: String -> State Console ()
displayOutput s = do
  console <- get
  put $ console { outputs = s : outputs console }

这只会使用提供的String更新状态。

您还需要一种在State Console monad中运行测试的方法:

runStateTest :: State Console a -> a
runStateTest = flip evalState $ Console [] []

这总是以空inputs和空outputs开始任何测试,因此作为测试作者,您有责任确保inputs始终以"quit"结尾。您也可以编写一个辅助函数来执行此操作,或更改runStateTest以始终包含此值。

那么一个简单的测试是:

tests :: [Test]
tests = [
  "Quit right away" ~: runStateTest $ do
    modify $ \console -> console { inputs = ["quit"] }

    mainImp getInput displayOutput

    Console actual _ <- get
    return $ elem "goodbye" actual @? "\"goodbye\" wasn't found in " ++ show actual
-- other tests go here...
]

此测试只是验证如果您立即"quit",则出现"goodbye"消息。

涉及程度更高的测试可能是:

,"Run single calcuation" ~: runStateTest $ do
    modify $ \console -> console { inputs = ["3*(2+2)","quit"] }

    mainImp getInput displayOutput

    Console actual _ <- get
    let expected =
          [ "What should I calcuclate? ex 3*(2+2)  | quit to exit","12","What should I calcuclate? ex 3*(2+2)  | quit to exit","goodbye"]
    return $ expected ~=? reverse actual

您可以将其插入到以上]列表的结尾tests之前,其中注释为-- other tests go here...

关于与Haskell进行单元测试的文章比我链接的文章多,因此请确保遵循那里的链接,并调查其他the Haskell tag和{{3 }}。

本文链接:https://www.f2er.com/3144203.html

大家都在问