Go: Автоматическое тестирование

Теория: Тестирование ошибок и паник

Ошибки и паники — это два разных уровня поломок в программе. Ошибка — это ожидаемая ситуация, которую можно обработать. Пользователь ввёл кривые данные, файл не открылся, база недоступна — всё это нормально, функция вернула ошибку, а вызывающий код решает, что с ней делать.

Паника — это авария. Программа оказалась в состоянии, где продолжать работу уже нельзя. Например, деление на ноль в функции, которая по контракту должна «всегда делить», или сломанное внутреннее состояние. В Go паника выбивается с помощью panic.

В тестах важно проверять и то, и другое: иногда функция должна вернуть ошибку, а иногда она обязана упасть.

Ошибки: встроенный тип error

В Go ошибки — это обычные значения. Есть встроенный интерфейс:

type error interface {
	Error() string
}

То есть всё, что умеет возвращать строку через метод Error(), считается ошибкой. Чаще всего используют errors.New("...") или fmt.Errorf("..."), чтобы создать ошибку.

Функции обычно возвращают результат и ошибку:

package calc

import "errors"

// Divide делит одно число на другое.
// Если знаменатель равен нулю — возвращаем ошибку.
func Divide(a, b int) (int, error) {
	if b == 0 {
		return 0, errors.New("division by zero")
	}
	return a / b, nil
}

Если всё хорошо — ошибка равна nil. Если что-то пошло не так — вторая переменная хранит ошибку с описанием.

Проверка ошибок в тестах

Когда мы пишем тест, важно проверить не только «счастливый путь», но и то, как функция реагирует на неправильный ввод.

package calc_test

import (
	"calc"
	"testing"
)

func TestDivide_Error(t *testing.T) {
	_, err := calc.Divide(10, 0)

	// Проверка №1: ошибка должна быть.
	// Если её нет — дальше проверять нечего.
	if err == nil {
		t.Fatal("ожидали ошибку, но получили nil")
	}

	// Проверка №2: если ошибка есть, проверяем её текст.
	// Сравнение через err.Error().
	want := "division by zero"
	if err.Error() != want {
		t.Errorf("получили %q, а хотели %q", err.Error(), want)
	}
}

Почему t.Fatal и t.Errorf разные?

  • t.Fatalt.Fatalf) — это экстренный стоп. Тест немедленно прерывается. Мы используем его там, где без этого дальше нет смысла. В примере выше — если ошибки вообще нет, то сравнивать её текст бессмысленно.
  • t.Errort.Errorf) — это мягкий сигнал: «что-то не так, но давай посмотрим дальше». Тест помечается как упавший, но выполнение продолжается. Это удобно, если ошибка есть, но сообщение отличается — тогда мы хотя бы увидим, что ещё делал тест.

Нужно запомнить правило: Fatal — для критичных проверок, Error — для уточняющих.

Паники: аварийные ситуации

Иногда функция должна не вернуть ошибку, а «завалиться» с паникой. Это используется редко, но бывают такие места, где программа не имеет смысла без аварийной остановки.

package calc

// MustDivide работает строго: если знаменатель 0 — сразу паника.
func MustDivide(a, b int) int {
	if b == 0 {
		panic("division by zero")
	}
	return a / b
}

Проверка паник в тестах

Если такую функцию вызвать напрямую, весь тест упадёт. Чтобы проверить панику, нужно её поймать. Для этого есть связка defer + recover.

func TestMustDivide_Panic(t *testing.T) {
	defer func() {
		// recover ловит панику, если она была.
		if r := recover(); r == nil {
			t.Fatal("ожидали панику, но её не было")
		} else if r != "division by zero" {
			t.Errorf("неожиданное сообщение паники: %v", r)
		}
	}()

	// Этот вызов должен вызвать панику.
	// Если паники не будет, defer не сработает как мы хотим.
	_ = calc.MustDivide(10, 0)
}

Схема работы простая:

  1. Ставим defer с анонимной функцией.
  2. Внутри неё вызываем recover().
  3. Если recover() вернул nil → паники не было → тест провален.
  4. Если вернул строку → проверяем, что она совпадает с ожидаемой.

Когда ошибка, а когда паника?

Ошибки — это часть нормального хода программы. Мы можем их обработать и продолжить работу:

  • Пользователь не ввёл пароль → показали сообщение.
  • Файл не найден → создали новый.
  • Подключение к БД не вышло → повторили через секунду.

Паники — это аварии. Тут уже нельзя «показать подсказку и продолжить». Надо валить выполнение или хотя бы остановить конкретный кусок.

  • Нарушены внутренние инварианты.
  • В функцию передали данные, с которыми она категорически не умеет работать.
  • Ошибка в инициализации, без которой приложение бессмысленно.

В тестах важно фиксировать и такие сценарии: что код не просто «как-то себя ведёт», а строго выдаёт ошибку или строго падает.

Ошибки в Go — это встроенный механизм, с которым мы сталкиваемся постоянно. Паники — более редкий инструмент, но их тоже нужно уметь проверять.

Ошибки проверяются через if err != nil и err.Error(). Паники — через defer и recover. В тестах t.Fatal используют для критичных проверок, когда без этого тест теряет смысл, а t.Errorf — для уточняющих сравнений.

Так мы получаем тесты, которые покрывают весь спектр: и удачные сценарии, и ошибки, и аварийные ситуации.

Рекомендуемые программы

+7 800 100 22 47

бесплатно по РФ

+7 495 085 21 62

бесплатно по Москве

108813 г. Москва, вн.тер.г. поселение Московский,
г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3
ОГРН 1217300010476
ИНН 7325174845