You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 16:30:24