Мы оборачиваем io.Reader с помощью io.TimeoutReader. Как уже говорилось, второе чтение завершится неудачей. Если мы запустим этот тест, чтобы убедиться, что функция толерантна к ошибкам, то получим провал теста. Действительно, io.ReadAll возвращает все ошибки, которые встречает.
Зная это, реализуем пользовательскую функцию ReadAll, которая терпима к n ошибкам:
func readAll(r io.Reader, retries int) ([]byte, error) {
b := make([]byte, 0, 512)
for {
if len(b) == cap(b) {
b = append(b, 0)[:len(b)]
}
n, err := r.Read(b[len(b):cap(b)])
b = b[:len(b)+n]
if err != nil {
if err == io.EOF {
return b, nil
}
retries--
if retries < 0 { Допускает повторные попытки
return b, err
}
}
}
}
Эта реализация похожа на io.ReadAll, но также обрабатывает настраиваемые повторные попытки. Если изменить реализацию исходной функции, чтобы использовать собственный readAll вместо io.ReadAll, тест больше не будет заканчиваться неудачей:
func foo(r io.Reader) error {
b, err := readAll(r, 3) До трех повторных попыток
if err != nil {
return err
}
// ...
}
Мы рассмотрели пример того, как проверить, что функция толерантна к ошибкам при чтении из io.Reader, выполнив тест с использованием пакета iotest.
При выполнении ввода/вывода и при работе с io.Reader и io.Writer помните, насколько удобен пакет iotest. Как мы видели, он предоставляет утилиты для проверки поведения пользовательского io.Reader и проверки нашего приложения на наличие ошибок, возникающих при чтении или записи данных.