最直接方法是用 os.pipe 临时替换 os.stdin/os.stdout:先保存原句柄,调用前替换,测试后立即恢复;写端需显式 close() 触发 eof,避免 scanner 阻塞;ansi 序列照常输出,断言时注意处理不可见字符。

用 os.Pipe 拦截 os.Stdin 和 os.Stdout 最直接
Go 程序默认从终端读写,测试时没法手动敲输入或捕获输出。最干净的办法是把 os.Stdin 和 os.Stdout 临时替换成管道(pipe),让测试代码控制流向。
关键点在于:必须在调用被测函数前替换,且测试结束后恢复原值,否则会影响其他测试或 panic(比如多次关闭同一 os.Stdin)。
- 替换前保存原始句柄:
oldStdin := os.Stdin、oldStdout := os.Stdout - 用
os.Pipe()创建一对连接,写端写入模拟输入,读端读取程序输出 - 注意
os.Stdin是*os.File,赋值时类型要一致;别误用strings.NewReader直接塞给它——会编译失败 - 测试完务必
os.Stdin = oldStdin、os.Stdout = oldStdout,尤其在并发测试中漏恢复会导致不可预测行为
fmt.Scan / bufio.Scanner 在管道里卡住?记得关写端
用 os.Pipe() 模拟输入时,如果被测代码用 fmt.Scan 或 bufio.Scanner 读取,常出现阻塞不返回——根本原因是管道的写端没关闭,读端一直等 EOF。
这不是 bug,是管道语义:只有写端关闭,读端才会收到 io.EOF,Scanner.Scan() 才返回 false。
立即学习“go语言免费学习笔记(深入)”;
- 写完模拟输入后,立刻调用
stdinPipeWriter.Close() - 不要依赖
defer关闭写端——它会在函数退出时才触发,而读操作可能早已卡住 - 若需多行输入,用
\n分隔,但最后一行也得换行,否则Scanner可能不触发最后一次Scan() -
fmt.Scanf类函数对换行更敏感,建议统一用bufio.Scanner+ 显式Close()
测试含 ANSI 转义序列的输出(如颜色、清屏)怎么办
很多 CLI 工具会输出 ANSI 控制码(比如 \033[32m 绿色文字),但 os.Stdout 替换为管道后,这些序列照常写出,不会被过滤或渲染——这反而是好事,说明你真能测到原始输出。
问题在于断言时容易忽略这些不可见字符,导致字符串比对失败。
- 用
strings.TrimSpace前先确认是否要保留空格/换行——有些逻辑依赖末尾\n - 若需忽略颜色,可用正则
regexp.MustCompile(`\033\[[0-9;]*m`)清洗,但仅限断言阶段,别改原始输出流 - 注意 Windows 默认终端不支持 ANSI,但 Go 的
os.Stdout写入管道时与平台无关,测试本身不受影响 - 避免用
fmt.Print直接输出带颜色的字符串做断言基准——手写 ANSI 容易出错,建议从真实运行结果截取
想测交互式 prompt(如 “Enter password:”)该怎么写断言
带提示文本的交互(比如 fmt.Print("Password: ") 后再 Scanln)会让测试变复杂:你既要验证提示是否输出,又要注入输入,还不能混淆两者的顺序。
核心是分两步断言:先读提示,再写输入,最后读结果。不能指望一次读完全部输出。
- 用
bufio.NewReader(stdoutPipeReader),配合ReadString('\n')或ReadBytes('\n')逐段读取 - 提示文本末尾是否有
\n很关键——fmt.Print不加换行,fmt.Println加,断言字符串要严格匹配 - 写输入后仍需关闭写端,否则后续读操作可能挂起;若 prompt 后还有更多输出,确保读取足够字节或用超时控制
- 别在测试里 sleep 等输出——管道是同步的,只要写端关了、缓冲区有数据,读就会立即返回
真实 CLI 的交互逻辑越深,越容易漏掉某次读或某次关。最稳妥的方式是把“写输入→关写端→读提示→读结果”打包成小函数,每次只专注一个交互回合。










