testing/iotest 包详解
概述
testing/iotest 包实现了主要用于测试的 Readers 和 Writers。
核心功能:
- 测试辅助 Reader(ErrReader、HalfReader、OneByteReader 等)
- 测试辅助 Writer(TruncateWriter)
- Reader 测试工具(TestReader)
- 日志记录 Reader/Writer(NewReadLogger、NewWriteLogger)
- 错误处理工具(DataErrReader)
- 超时模拟(TimeoutReader)
重要说明:
- ✅ Go 版本:所有 Go 版本都支持
- ✅ 测试用途:主要用于测试 io.Reader 和 io.Writer 实现
- ✅ 错误模拟:可以模拟各种错误场景
- ⚠️ 生产环境:不推荐在生产环境使用
包导入
import "testing/iotest"
变量
ErrTimeout
var ErrTimeout = errors.New("timeout")
功能: 假的超时错误。
用途: 用于 TimeoutReader 返回的超时错误。
函数详解(按 A-Z 分类)
D
DataErrReader
func DataErrReader(r io.Reader) io.Reader
功能: 改变 Reader 的错误处理方式。
参数:
r io.Reader- 要包装的 Reader
返回值:
io.Reader- 包装后的 Reader
行为改变:
- 普通 Reader:在读取完所有数据后的第一次 Read 调用返回错误(通常是 EOF)
- DataErrReader:在最后一次 Read 调用时同时返回数据和错误
示例:
package main
import (
"fmt"
"io"
"strings"
"testing/iotest"
)
func main() {
// 普通 Reader
r := strings.NewReader("hello")
buf := make([]byte, 10)
n, err := r.Read(buf)
fmt.Printf("普通:%d, %v\n", n, err) // 5, <nil>
n, err = r.Read(buf)
fmt.Printf("普通:%d, %v\n", n, err) // 0, EOF
// DataErrReader
r = strings.NewReader("hello")
der := iotest.DataErrReader(r)
n, err = der.Read(buf)
fmt.Printf("DataErr:%d, %v\n", n, err) // 5, EOF
}
E
ErrReader
func ErrReader(err error) io.Reader
功能: 返回一个 io.Reader,所有 Read 调用都返回 0, err。
参数:
err error- 要返回的错误
返回值:
io.Reader- 总是返回错误的 Reader
示例:
package main
import (
"errors"
"fmt"
"io"
"testing/iotest"
)
func main() {
// 自定义错误
r := iotest.ErrReader(errors.New("custom error"))
buf := make([]byte, 10)
n, err := r.Read(buf)
fmt.Printf("n: %d\n", err) // n: 0
fmt.Printf("err: %v\n", err) // err: custom error
// 第二次调用仍然返回相同错误
n, err = r.Read(buf)
fmt.Printf("n: %d, err: %v\n", n, err) // 0, custom error
}
运行结果:
n: 0
err: custom error
H
HalfReader
func HalfReader(r io.Reader) io.Reader
功能: 返回一个 Reader,每次只读取请求字节数的一半。
参数:
r io.Reader- 要包装的 Reader
返回值:
io.Reader- 每次读取一半字节的 Reader
用途: 测试 Reader 处理部分读取的情况。
示例:
package main
import (
"fmt"
"io"
"strings"
"testing/iotest"
)
func main() {
r := strings.NewReader("12345678")
hr := iotest.HalfReader(r)
buf := make([]byte, 8)
// 第一次请求 8 字节,实际读取 4 字节
n, _ := hr.Read(buf)
fmt.Printf("读取:%d 字节:%s\n", n, buf[:n]) // 读取:4 字节:1234
// 第二次请求 8 字节,实际读取 2 字节
n, _ = hr.Read(buf)
fmt.Printf("读取:%d 字节:%s\n", n, buf[:n]) // 读取:2 字节:56
// 继续...
n, _ = hr.Read(buf)
fmt.Printf("读取:%d 字节:%s\n", n, buf[:n]) // 读取:1 字节:7
n, _ = hr.Read(buf)
fmt.Printf("读取:%d 字节:%s\n", n, buf[:n]) // 读取:1 字节:8
}
N
NewReadLogger
func NewReadLogger(prefix string, r io.Reader) io.Reader
功能: 返回一个 Reader,记录每次读取到标准错误。
参数:
prefix string- 日志前缀r io.Reader- 要包装的 Reader
返回值:
io.Reader- 带日志的 Reader
日志格式: 使用前缀和十六进制格式记录读取的数据。
示例:
package main
import (
"strings"
"testing/iotest"
)
func main() {
r := strings.NewReader("hello world")
lr := iotest.NewReadLogger("READ: ", r)
buf := make([]byte, 5)
lr.Read(buf)
// 标准错误输出:
// READ: 68656c6c6f
}
NewWriteLogger
func NewWriteLogger(prefix string, w io.Writer) io.Writer
功能: 返回一个 Writer,记录每次写入到标准错误。
参数:
prefix string- 日志前缀w io.Writer- 要包装的 Writer
返回值:
io.Writer- 带日志的 Writer
日志格式: 使用前缀和十六进制格式记录写入的数据。
示例:
package main
import (
"bytes"
"testing/iotest"
)
func main() {
var buf bytes.Buffer
lw := iotest.NewWriteLogger("WRITE: ", &buf)
lw.Write([]byte("hello"))
// 标准错误输出:
// WRITE: 68656c6c6f
}
O
OneByteReader
func OneByteReader(r io.Reader) io.Reader
功能: 返回一个 Reader,每次非空读取只读取一个字节。
参数:
r io.Reader- 要包装的 Reader
返回值:
io.Reader- 每次读取一字节的 Reader
用途: 测试 Reader 处理逐字节读取的情况。
示例:
package main
import (
"fmt"
"io"
"strings"
"testing/iotest"
)
func main() {
r := strings.NewReader("hello")
obr := iotest.OneByteReader(r)
buf := make([]byte, 10)
for {
n, err := obr.Read(buf)
if err == io.EOF {
break
}
fmt.Printf("读取:%d 字节:%s\n", n, buf[:n])
}
// 输出:
// 读取:1 字节:h
// 读取:1 字节:e
// 读取:1 字节:l
// 读取:1 字节:l
// 读取:1 字节:o
}
T
TestReader
func TestReader(r io.Reader, content []byte) error
功能: 测试从 r 读取是否返回预期的文件内容。
参数:
r io.Reader- 要测试的 Readercontent []byte- 预期的内容
返回值:
error- 如果发现异常则返回错误
测试内容:
- 执行不同大小的读取直到 EOF
- 如果 r 实现 io.ReaderAt,测试 ReadAt
- 如果 r 实现 io.Seeker,测试 Seek 操作
- 检查所有读取行为是否符合预期
示例:
package main
import (
"fmt"
"strings"
"testing/iotest"
)
func main() {
// 测试正确的 Reader
r := strings.NewReader("hello")
err := iotest.TestReader(r, []byte("hello"))
if err != nil {
fmt.Println("测试失败:", err)
} else {
fmt.Println("测试通过")
}
// 测试错误的 Reader
r = strings.NewReader("world")
err = iotest.TestReader(r, []byte("hello"))
if err != nil {
fmt.Println("测试失败:", err)
}
}
TimeoutReader
func TimeoutReader(r io.Reader) io.Reader
功能: 返回一个 Reader,第二次读取时无数据返回 ErrTimeout。
参数:
r io.Reader- 要包装的 Reader
返回值:
io.Reader- 模拟超时的 Reader
行为:
- 第一次读取:正常
- 第二次读取(无数据):返回 ErrTimeout
- 后续调用:成功
示例:
package main
import (
"fmt"
"io"
"strings"
"testing/iotest"
)
func main() {
r := strings.NewReader("hello")
tr := iotest.TimeoutReader(r)
buf := make([]byte, 10)
// 第一次读取:正常
n, err := tr.Read(buf)
fmt.Printf("第一次:%d, %v\n", n, err) // 5, <nil>
// 第二次读取:超时
n, err = tr.Read(buf)
fmt.Printf("第二次:%d, %v\n", n, err) // 0, timeout
// 第三次读取:成功(EOF)
n, err = tr.Read(buf)
fmt.Printf("第三次:%d, %v\n", n, err) // 0, EOF
}
W
TruncateWriter
func TruncateWriter(w io.Writer, n int64) io.Writer
功能: 返回一个 Writer,写入 n 字节后静默停止。
参数:
w io.Writer- 要包装的 Writern int64- 最大写入字节数
返回值:
io.Writer- 截断的 Writer
行为:
- 前 n 字节:正常写入
- 超过 n 字节:静默丢弃(不返回错误)
示例:
package main
import (
"bytes"
"fmt"
"testing/iotest"
)
func main() {
var buf bytes.Buffer
tw := iotest.TruncateWriter(&buf, 5)
n, _ := tw.Write([]byte("hello world"))
fmt.Printf("写入:%d 字节\n", n) // 写入:11 字节
fmt.Printf("缓冲区:%q\n", buf.String()) // 缓冲区:"hello"
// 再次写入
n, _ = tw.Write([]byte("more"))
fmt.Printf("写入:%d 字节\n", n) // 写入:4 字节
fmt.Printf("缓冲区:%q\n", buf.String()) // 缓冲区:"hello"(不变)
}
典型示例
示例 1:测试 Reader 实现
package myio_test
import (
"bytes"
"testing"
"testing/iotest"
)
func TestMyReader(t *testing.T) {
content := []byte("hello world")
r := bytes.NewReader(content)
// 测试 Reader 行为
if err := iotest.TestReader(r, content); err != nil {
t.Fatal(err)
}
}
func TestMyReaderWithErrors(t *testing.T) {
content := []byte("test")
// 测试各种 Reader 包装
tests := []struct {
name string
r io.Reader
}{
{"normal", bytes.NewReader(content)},
{"half", iotest.HalfReader(bytes.NewReader(content))},
{"onebyte", iotest.OneByteReader(bytes.NewReader(content))},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := iotest.TestReader(tt.r, content); err != nil {
t.Errorf("%s: %v", tt.name, err)
}
})
}
}
示例 2:模拟错误场景
package myio_test
import (
"errors"
"io"
"testing"
"testing/iotest"
)
func TestReadWithError(t *testing.T) {
// 模拟读取错误
errReader := iotest.ErrReader(errors.New("network error"))
buf := make([]byte, 10)
n, err := errReader.Read(buf)
if n != 0 {
t.Errorf("expected 0 bytes, got %d", n)
}
if err == nil {
t.Error("expected error, got nil")
}
}
func TestReadWithTimeout(t *testing.T) {
r := iotest.TimeoutReader(bytes.NewReader([]byte("data")))
buf := make([]byte, 10)
// 第一次读取成功
n, err := r.Read(buf)
if n != 4 || err != nil {
t.Errorf("first read: got %d, %v; want 4, nil", n, err)
}
// 第二次读取超时
n, err = r.Read(buf)
if n != 0 || err != iotest.ErrTimeout {
t.Errorf("second read: got %d, %v; want 0, timeout", n, err)
}
}
示例 3:测试部分读取
package myio_test
import (
"bytes"
"io"
"testing"
"testing/iotest"
)
// 测试 Reader 能正确处理部分读取
func TestPartialRead(t *testing.T) {
content := []byte("01234567")
r := iotest.HalfReader(bytes.NewReader(content))
buf := make([]byte, 8)
var result []byte
for {
n, err := r.Read(buf)
if n > 0 {
result = append(result, buf[:n]...)
}
if err == io.EOF {
break
}
if err != nil {
t.Fatal(err)
}
}
if !bytes.Equal(result, content) {
t.Errorf("got %q, want %q", result, content)
}
}
示例 4:测试逐字节读取
package myio_test
import (
"bytes"
"io"
"testing"
"testing/iotest"
)
func TestByteByByteRead(t *testing.T) {
content := []byte("hello")
r := iotest.OneByteReader(bytes.NewReader(content))
var result []byte
buf := make([]byte, 1)
for {
n, err := r.Read(buf)
if n > 0 {
result = append(result, buf[:n]...)
}
if err == io.EOF {
break
}
if err != nil {
t.Fatal(err)
}
}
if !bytes.Equal(result, content) {
t.Errorf("got %q, want %q", result, content)
}
}
示例 5:测试 Writer 截断
package myio_test
import (
"bytes"
"testing"
"testing/iotest"
)
func TestTruncateWriter(t *testing.T) {
var buf bytes.Buffer
tw := iotest.TruncateWriter(&buf, 5)
// 写入超过限制
n, err := tw.Write([]byte("hello world"))
if err != nil {
t.Fatal(err)
}
if n != 11 {
t.Errorf("Write returned %d, want 11", n)
}
// 检查实际内容
if buf.String() != "hello" {
t.Errorf("got %q, want %q", buf.String(), "hello")
}
}
示例 6:日志记录 Reader/Writer
package myio_test
import (
"bytes"
"io"
"testing"
"testing/iotest"
)
func TestWithLogger(t *testing.T) {
content := []byte("hello")
r := iotest.NewReadLogger("READ: ", bytes.NewReader(content))
var buf bytes.Buffer
w := iotest.NewWriteLogger("WRITE: ", &buf)
// 复制数据(会打印日志)
_, err := io.Copy(w, r)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(buf.Bytes(), content) {
t.Errorf("got %q, want %q", buf.Bytes(), content)
}
}
示例 7:测试 DataErrReader
package myio_test
import (
"bytes"
"io"
"testing"
"testing/iotest"
)
func TestDataErrReader(t *testing.T) {
content := []byte("test data")
r := iotest.DataErrReader(bytes.NewReader(content))
buf := make([]byte, len(content)+1)
// 一次性读取所有数据和 EOF
n, err := r.Read(buf)
if n != len(content) {
t.Errorf("got %d bytes, want %d", n, len(content))
}
if err != io.EOF {
t.Errorf("got error %v, want EOF", err)
}
if !bytes.Equal(buf[:n], content) {
t.Errorf("got %q, want %q", buf[:n], content)
}
}
最佳实践
1. 使用 TestReader 验证 Reader 实现
// ✅ 推荐:使用 TestReader
func TestMyReader(t *testing.T) {
content := []byte("expected content")
r := NewMyReader(content)
if err := iotest.TestReader(r, content); err != nil {
t.Fatal(err)
}
}
// ❌ 不推荐:手动测试所有场景
func TestMyReader(t *testing.T) {
// 手动测试各种读取大小...
// 手动测试 Seek...
// 手动测试 ReadAt...
}
2. 使用 ErrReader 模拟错误
// ✅ 推荐:使用 ErrReader
func TestReadError(t *testing.T) {
r := iotest.ErrReader(errors.New("custom error"))
// 测试错误处理
}
// ❌ 不推荐:创建自定义错误 Reader
type errorReader struct{}
func (e errorReader) Read(p []byte) (int, error) {
return 0, errors.New("custom error")
}
3. 使用 HalfReader/OneByteReader 测试边界
// ✅ 推荐:测试部分读取场景
func TestPartialRead(t *testing.T) {
r := iotest.HalfReader(realReader)
// 测试部分读取处理
}
// ✅ 推荐:测试逐字节读取
func TestByteRead(t *testing.T) {
r := iotest.OneByteReader(realReader)
// 测试逐字节处理
}
4. 使用 TruncateWriter 测试写入限制
// ✅ 推荐:使用 TruncateWriter
func TestWriteLimit(t *testing.T) {
tw := iotest.TruncateWriter(realWriter, 100)
// 测试写入限制处理
}
与其他包配合
与 io 包配合
package main
import (
"bytes"
"io"
"testing/iotest"
)
func main() {
// 使用 io.Copy 测试
r := iotest.OneByteReader(bytes.NewReader([]byte("hello")))
var buf bytes.Buffer
io.Copy(&buf, r)
}
与 bytes 包配合
package main
import (
"bytes"
"testing/iotest"
)
func main() {
// 组合使用
r := iotest.HalfReader(bytes.NewReader([]byte("data")))
// 测试...
}
注意事项
限制
-
测试用途:
- 主要用于测试,不推荐生产使用
- 性能不是主要考虑因素
-
日志输出:
- NewReadLogger 和 NewWriteLogger 输出到标准错误
- 可能影响测试输出
-
错误模拟:
- TimeoutReader 的超时行为是固定的
- 不能自定义超时条件
使用建议
-
组合使用:
// 可以组合多个包装器 r := iotest.OneByteReader( iotest.HalfReader( bytes.NewReader(data), ), ) -
错误检查:
// 使用 errors.Is 检查超时 if errors.Is(err, iotest.ErrTimeout) { // 处理超时 } -
测试覆盖:
// 测试各种 Reader 行为 tests := []io.Reader{ normalReader, iotest.HalfReader(normalReader), iotest.OneByteReader(normalReader), iotest.TimeoutReader(normalReader), }
快速参考
Reader 包装器
| 函数 | 功能 | 用途 |
|---|---|---|
ErrReader(err) | 总是返回错误 | 错误处理测试 |
HalfReader(r) | 每次读取一半 | 部分读取测试 |
OneByteReader(r) | 每次读取一字节 | 逐字节读取测试 |
TimeoutReader(r) | 模拟超时 | 超时处理测试 |
DataErrReader(r) | 数据和错误同时返回 | 边界条件测试 |
Writer 包装器
| 函数 | 功能 | 用途 |
|---|---|---|
TruncateWriter(w, n) | 写入 n 字节后停止 | 写入限制测试 |
日志工具
| 函数 | 功能 |
|---|---|
NewReadLogger(prefix, r) | 记录读取操作 |
NewWriteLogger(prefix, w) | 记录写入操作 |
测试工具
| 函数 | 功能 |
|---|---|
TestReader(r, content) | 测试 Reader 实现 |
错误类型
| 错误 | 描述 |
|---|---|
ErrTimeout | 超时错误 |
常见用法
// 1. 测试 Reader
err := iotest.TestReader(myReader, expectedContent)
// 2. 模拟错误
r := iotest.ErrReader(errors.New("custom error"))
// 3. 测试部分读取
r := iotest.HalfReader(realReader)
// 4. 测试逐字节读取
r := iotest.OneByteReader(realReader)
// 5. 测试超时处理
r := iotest.TimeoutReader(realReader)
// 6. 测试写入限制
w := iotest.TruncateWriter(realWriter, 100)
// 7. 调试读取
r = iotest.NewReadLogger("DEBUG: ", r)
// 8. 调试写入
w = iotest.NewWriteLogger("DEBUG: ", w)
总结
testing/iotest 包是 Go 标准库中用于测试 io.Reader 和 io.Writer 实现的辅助包。
核心优势:
- ✅ 提供多种测试用 Reader/Writer
- ✅ 模拟各种边界条件和错误
- ✅ 自动测试 Reader 实现(TestReader)
- ✅ 日志记录便于调试
重要限制:
- ⚠️ 仅用于测试,不推荐生产使用
- ⚠️ 性能不是主要考虑因素
- ⚠️ 日志输出到标准错误
主要用途:
- 测试 Reader 实现正确性
- 模拟错误场景(EOF、超时等)
- 测试部分读取处理
- 测试写入限制
- 调试 I/O 操作
使用建议:
- 使用 TestReader 验证 Reader 实现
- 使用 ErrReader 模拟错误处理
- 使用 HalfReader/OneByteReader 测试边界
- 使用 TruncateWriter 测试写入限制
- 使用日志工具调试 I/O 问题
典型用法:
// 测试 Reader 实现
if err := iotest.TestReader(myReader, content); err != nil {
t.Fatal(err)
}
// 模拟错误
r := iotest.ErrReader(errors.New("network error"))
// 测试部分读取
r := iotest.HalfReader(realReader)