GitHub Autograder中Tasty测试Haskell函数遇句柄写入异常
解决Haskell交互式IO函数的测试句柄问题
目标测试函数
我们要测试的Haskell函数是一个交互式输入的字符计数函数:
countLetters :: IO [Int] countLetters = do putStr "First entry: " x <- getLine putStr "Second entry: " y <- getLine putStr "Third entry: " z <- getLine return [length x, length y, length z]
原测试代码与问题
原测试尝试修改stdin句柄,但触发了模式不匹配的异常:
testCase "test_countLetters" $ do h <- openFile "/dev/null" ReadMode hDuplicateTo h stdin hPutStrLn stdin "Haskell Is Fun" result <- countLetters hClose h assertEqual "Count Letters" [7,2,3] result
报错信息:
Exception: <stdin>: hPutStr: illegal operation (handle is not open for writing)
切换为WriteMode后,又会因为getLine无法读取只读句柄而失败——核心问题是stdin默认是只读句柄,无法写入;强行修改模式会破坏其读取能力。
正确解决方案
核心思路是:给stdin替换一个包含预设测试输入的可读句柄,而非直接修改原stdin的模式。以下是两种可行实现:
方法一:使用临时文件存储测试输入
import System.IO import Test.Tasty import Test.Tasty.HUnit countLetters :: IO [Int] countLetters = do putStr "First entry: " x <- getLine putStr "Second entry: " y <- getLine putStr "Third entry: " z <- getLine return [length x, length y, length z] testCase "test_countLetters" $ do -- 写入测试输入到临时文件(每行对应一个交互式输入) let testInput = "Haskell\nIs\nFun\n" withFile "test_input.txt" WriteMode $ \h -> do hPutStr h testInput -- 保存原stdin,替换为临时文件的可读句柄 originalStdin <- hDuplicate stdin testInputHandle <- openFile "test_input.txt" ReadMode hDuplicateTo testInputHandle stdin -- 执行测试并验证结果 result <- countLetters assertEqual "Count Letters" [7,2,3] result -- 恢复原stdin并清理临时文件 hDuplicateTo originalStdin stdin hClose testInputHandle removeFile "test_input.txt"
方法二:使用管道(无磁盘IO,更适合自动化测试)
import System.IO import Test.Tasty import Test.Tasty.HUnit countLetters :: IO [Int] countLetters = do putStr "First entry: " x <- getLine putStr "Second entry: " y <- getLine putStr "Third entry: " z <- getLine return [length x, length y, length z] testCase "test_countLetters" $ do -- 创建管道:readHandle用于读取输入,writeHandle用于写入测试数据 (readHandle, writeHandle) <- createPipe hSetBuffering writeHandle LineBuffering -- 设置行缓冲确保输入立即生效 -- 写入测试输入到管道写端,然后关闭写端标记输入结束 hPutStr writeHandle "Haskell\nIs\nFun\n" hClose writeHandle -- 保存原stdin,替换为管道读端 originalStdin <- hDuplicate stdin hDuplicateTo readHandle stdin -- 执行测试并验证结果 result <- countLetters assertEqual "Count Letters" [7,2,3] result -- 恢复原stdin并清理句柄 hDuplicateTo originalStdin stdin hClose readHandle
关键注意事项
- 测试完成后必须恢复原
stdin,避免影响后续测试或程序的正常行为。 - 管道方法无需创建磁盘文件,更适合GitHub Autograder这类自动化测试环境。
- 测试输入的换行符
\n要与getLine的读取逻辑匹配,确保每个输入对应一行。
内容的提问来源于stack exchange,提问作者Emily Miller
相关产品推荐
相关产品推荐

