简体   繁体   English

Hunit测试例外

[英]Hunit testing with exceptions

I am writing a program to parse some xml. 我正在编写一个程序来解析一些xml。

I took the approach of using MonadThrow to take care of errors in parsing, but now when testing the fails - can't figure out how to test them. 我采取了使用MonadThrow来处理解析错误的方法,但是现在测试失败时-无法弄清楚如何测试它们。 Which makes me unsure if this approach is the right one. 这使我不确定这种方法是否正确。

First of all here is a complete (non-working) example 首先,这里是一个完整的(无效)示例

exception.hs

{-# LANGUAGE OverloadedStrings #-}

import Test.Tasty
import Test.Tasty.HUnit

import Control.Exception (SomeException, displayException)
import Control.Monad (unless)
import Control.Monad.Trans.Resource (MonadThrow)
import Data.Function (on)
import Text.XML (Element, parseText, def, documentRoot, elementName)
import Data.Text (Text)
import Data.Text.Lazy (fromStrict)

data TestElement = TestElement deriving (Show, Eq)

main :: IO ()
main = defaultMain unitTests

unitTests :: TestTree
unitTests = testGroup "Unit tests"
    [ testCase "parseTxt parser goodTxt1 == Right TestElement " $
        parseTxt parser goodTxt1 @?= Right TestElement
    , testCase "parseTxt parser goodTxt2 == Right TestElement " $
        parseTxt parser goodTxt2 @?= Right TestElement
    , testCase "parseTxt parser failTxt == Left \"ElementName does not match TestElement\"" $
        parseTxt parser failTxt @?= undefined
    --hunit
    ]


parseTxt :: (Element -> Either SomeException a) -> Text -> Either SomeException a
parseTxt parser inText = documentRoot <$> (parseText def $ fromStrict inText) >>=
                         parser

parser :: MonadThrow m => Element -> m TestElement
parser elmt =
    do unless (elementName elmt == "TestElement")
         $ fail "ElementName does not match TestElement"
       {-here usually some more complicated attribute/subnode parsing happens-}
       return TestElement

failTxt :: Text
failTxt = "<ToastElement></ToastElement>"

goodTxt1 :: Text
goodTxt1 = "<TestElement />"

goodTxt2 :: Text
goodTxt2 = "<TestElement></TestElement>"

instance Eq SomeException where
    (==) = (==) `on` displayException

which needs exception.cabal 这需要exception.cabal

[...]
executable exception
  hs-source-dirs:      src
  main-is:             Main.hs
  default-language:    Haskell2010
  build-depends:       base >= 4.7 && < 5
               ,       xml-conduit
               ,       exceptions
               ,       resourcet
               ,       tasty
               ,       tasty-hunit
               ,       text

TL;DR TL; DR

I am not sure what to put instead of the undefined in the last unit test and if the approach of using exceptions is right in this case. undefined在上一个单元测试中要放置什么而不是undefined ,以及在这种情况下使用异常的方法是否正确。


There are several options I thought of: 我想到了几种选择:

  • using (either displayException show $ parseTxt parser failTxt) @?= undefined still fails and does not yield a Left value 使用(either displayException show $ parseTxt parser failTxt) @?= undefined仍然会失败,并且不会产生Left
  • using assertFail defies the purpose of having a Either SomeException TestElement in my opinion 使用assertFail违抗有目的Either SomeException TestElement在我看来,
  • I could use a self-defined exception-type in order to match against it, but can I use fail to throw an error of my own type 我可以使用自定义的异常类型来与之匹配,但是我可以使用失败引发自己类型的错误

I think one of the sources of my confusion is that I don't know when the error is thrown (I thought lazy evaluation would throw the error when I matched against it - which is apparently wrong). 我认为我感到困惑的原因之一是我不知道何时引发错误(我认为当我将其与之匹配时,惰性评估会引发错误-显然是错误的)。

Thanks to @user2407038 I've been able to solve this: 感谢@ user2407038,我得以解决此问题:

defining a new datatype for the exception 为异常定义新的数据类型

data ParseException = TagMismatch String deriving (Typeable, Eq, Show)

then adjusting the imports and the following functions 然后调整导入和以下功能

parseTxt :: Exception e => (Element -> Either e a) -> Text -> Either SomeException a
parseTxt parser inText = documentRoot <$> (parseText def $ fromStrict inText) >>=
                         (first toException . parser)

first :: (a -> c) -> Either a b -> Either c b
first f (Left l) = Left (f l)
first _ (Right r) = Right r

parser :: MonadThrow m => Element -> m TestElement
parser elmt =
    do unless (elementName elmt == "TestElement")
         $ throwM $ TagMismatch "TestElement"
       return TestElement

unitTests :: TestTree
unitTests = testGroup "Unit tests"
    [ {-...-}
      testCase "parseTxt parser failTxt == fail" $
        (first aux $ parseTxt parser failTxt) @?= Left $ TagMismatch "TestElement"
    ]
    where aux = fromMaybe (error "converting from SomeException failed")
              . fromException

Note1: the deriving Eq is only necessary for the @?= operation in the unit tests and could be omitted for the productive version of the code. 注意1:仅当单元测试中的@?=运算时才需要deriving Eq ,对于有效版本的代码,则可以省略。

Note2: Also the direct dependency on resourcet can be replaced by exceptions , which the former just reexports. 注意2:同样,对resourcet的直接依赖性也可以由exceptions代替, exceptions只是重新导出。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM