Функции, которые возвращают ошибки
Многие функции в Go возвращают два значения: результат и ошибку error. При успехе ошибка равна nil; при неудаче результат — это нулевое значение, а ошибка объясняет, что пошло не так.
Тест для такой функции должен проверять оба пути: корректные входные данные возвращают правильное значение и ошибку nil, а некорректные действительно приводят к ошибке — функция, которая молча принимает неверные данные, это тоже ошибка.
Успешный путь
Для корректных входных данных сначала проверяйте ошибку: если она не nil, что-то пошло серьёзно не так и проверять значение уже бессмысленно — это задача для t.Fatalf. И только затем сравнивайте значение.
Корректные данные, ошибка nil
SafeDivide(10, 2) должна вернуть 5 и никакой ошибки. Сначала ошибка проверяется с помощью t.Fatalf, и только потом сравнивается значение.
Вывод: программа на Go и захваченный вывод
package calc
import (
"errors"
"testing"
)
func SafeDivide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("деление на ноль невозможно")
}
return a / b, nil
}
func TestSafeDivideGood(t *testing.T) {
got, err := SafeDivide(10, 2)
if err != nil {
t.Fatalf("неожиданная ошибка: %v", err)
}
if got != 5 {
t.Errorf("SafeDivide(10, 2) = %v; ожидалось 5", got)
}
}
Путь с ошибкой
Вторая половина контракта: некорректные входные данные должны приводить к ошибке. Тест вызывает функцию с такими данными и падает, если ошибка равна nil.
Значение результата игнорируется с помощью _ — когда возвращается ошибка, значение не несёт никакого смысла.
Некорректные данные должны вернуть ошибку
Деление на ноль обязано завершиться неудачей. Если err вернулась как nil, тест падает с t.Fatal — функция приняла данные, которые должна была отвергнуть.
Вывод: программа на Go и захваченный вывод
package calc
import (
"errors"
"testing"
)
func SafeDivide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("деление на ноль невозможно")
}
return a / b, nil
}
func TestSafeDivideByZero(t *testing.T) {
_, err := SafeDivide(10, 0)
if err == nil {
t.Fatal("ожидалась ошибка при делении на ноль, получено nil")
}
}
Таблица с полем wantErr
Оба пути помещаются в один табличный тест: добавьте каждому тест-кейсу поле wantErr, которое указывает, ожидается ли ошибка.
Цикл сначала проверяет сторону ошибки — получена ошибка там, где её не ждали, или её нет там, где она ожидалась, это фатально для данного подтеста. Затем для успешных случаев значение сравнивается как обычно.
Успешные и ошибочные случаи в одной таблице
Четыре случая: три обычных деления и одно деление на ноль с wantErr: true. Один цикл покрывает весь контракт функции.
Вывод: программа на Go и захваченный вывод
package calc
import (
"errors"
"testing"
)
func SafeDivide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("деление на ноль невозможно")
}
return a / b, nil
}
func TestSafeDivide(t *testing.T) {
cases := []struct {
name string
a, b float64
want float64
wantErr bool
}{
{"simple division", 10, 2, 5, false},
{"negative result", -9, 3, -3, false},
{"divide zero", 0, 5, 0, false},
{"divide by zero", 10, 0, 0, true},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got, err := SafeDivide(tc.a, tc.b)
if (err != nil) != tc.wantErr {
t.Fatalf("SafeDivide(%v, %v) ошибка = %v; ожидалась ошибка %v", tc.a, tc.b, err, tc.wantErr)
}
if !tc.wantErr && got != tc.want {
t.Errorf("SafeDivide(%v, %v) = %v; ожидалось %v", tc.a, tc.b, got, tc.want)
}
})
}
}