简体   繁体   中英

Haskell Tasty.HUnit : how to run multiple tests with IO

I'm trying to run multiple tests (that is, multiple Assertions) within a Test.Tasty testGroup; but feeding in a single "object" that has been read in from IO.

For example, I read & parse a file; and I want to make multiple assertions against the result of that file. Something like

tests :: [String] -> TestTree
tests ls = testGroup "tests" [ testCase "length" $ length ls @?= 2
                             , testCase "foo"    $ ls !! 0 @?= "foo"
                             ]

main = do
  ls :: [String] <- read <$> readFile "/tmp/hunit"
  defaultMain (tests ls)

However, the above requires that the IO is performed before calling the tests; and is performed even if only a subset of tests is requested (whether or not that subset actually uses the IO result).

Alternatively, each testCase can perform its own IO (an Assertion is just IO (), after all); but that potentially means IO being performed repeatedly, which is not what I want.

Alternatively again, a testCase can include a do {} block which calls multiple assertions; but this will mean that individual tests are not selectable, and won't get verbose output to confirm which tests were run.

Test.Tasty.withResource looks hopeful; and if its third argument were a -> TestTree , I could work with that; however, it isn't, it's IO a -> TestTree , and I'm struggling to work out how to safely extract the a to use in my test cases.

I've tried playing with this, but I fear I'm missing something fundamental...

Any help gratefully received.

Seems like it should be pretty simple; since type Assertion = IO () , these two pieces should be enough:

(>>=) :: IO a -> (a -> Assertion) -> Assertion
testCase :: TestName -> Assertion -> TestTree

You are right looking at the

withResource
  :: IO a -- ^ initialize the resource
  -> (a -> IO ()) -- ^ free the resource
  -> (IO a -> TestTree)
    -- ^ @'IO' a@ is an action which returns the acquired resource.
    -- Despite it being an 'IO' action, the resource it returns will be
    -- acquired only once and shared across all the tests in the tree.
  -> TestTree

The idea is that you can write your scenario as:

tests :: IO String -> TestTree
tests lsIO = testGroup "tests"
    [ testCase "length" $ do
        ls <- lsIO
        length ls @?= 2
    , testCase "foo"    $ do
        ls <- lsIO
        ls !! 0 @?= "foo"
    , testCase "no io" $ do
        return ()
    ]

main :: IO ()
main = defaultMain (withResource acquire tests)

acquire :: IO [String]
acquire = read <$> readFile "/tmp/hunit"

ie it looks like you'd read file multiple times, but tasty performs the action only once. That's what the comment says :) Try adding putStrLn "trace debug" to the acquire to see for sure, that it's run mostly once (ie not run if you only ask for no io test).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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