Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 - 要测试的 Reader
  • content []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 - 要包装的 Writer
  • n 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")))
    // 测试...
}

注意事项

限制

  1. 测试用途

    • 主要用于测试,不推荐生产使用
    • 性能不是主要考虑因素
  2. 日志输出

    • NewReadLogger 和 NewWriteLogger 输出到标准错误
    • 可能影响测试输出
  3. 错误模拟

    • TimeoutReader 的超时行为是固定的
    • 不能自定义超时条件

使用建议

  1. 组合使用

    // 可以组合多个包装器
    r := iotest.OneByteReader(
        iotest.HalfReader(
            bytes.NewReader(data),
        ),
    )
    
  2. 错误检查

    // 使用 errors.Is 检查超时
    if errors.Is(err, iotest.ErrTimeout) {
        // 处理超时
    }
    
  3. 测试覆盖

    // 测试各种 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 操作

使用建议

  1. 使用 TestReader 验证 Reader 实现
  2. 使用 ErrReader 模拟错误处理
  3. 使用 HalfReader/OneByteReader 测试边界
  4. 使用 TruncateWriter 测试写入限制
  5. 使用日志工具调试 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)