Тестування в Go у 2026: модульні тести, моки та питання технічних співбесід

Опануйте тестування в Go зі стандартною бібліотекою, табличними тестами, моками та патернами, які очікують на співбесідах. Практичні приклади з testify, gomock та пакетом testing.

Тестування Go модульні тести та моки для технічних співбесід

Тестування в Go базується на стандартному пакеті testing, який поставляється з кожною інсталяцією Go і не потребує жодних зовнішніх залежностей. На відміну від фреймворків в інших мовах, підхід Go надає перевагу простоті: тестові файли знаходяться поруч з продакшн кодом, тестові функції дотримуються конвенції іменування, а команда go test обробляє виявлення та виконання тестів.

На що звертають увагу інтерв'юери

На кандидатів, які пишуть табличні тести, використовують інтерфейси для впровадження залежностей і розуміють, коли мокування додає цінність, а коли лише зайву складність.

Написання модульних тестів з пакетом testing

Кожен тестовий файл закінчується на _test.go і знаходиться в тому ж пакеті, що й код, який тестується. Тестові функції починаються з Test, за яким йде назва з великої літери. Параметр *testing.T надає методи для звітування про помилки.

calculator_test.gogo
package calculator

import "testing"

func TestAdd(t *testing.T) {
    result := Add(2, 3)
    if result != 5 {
        t.Errorf("Add(2, 3) = %d; want 5", result)
    }
}

Виконання go test ./... запускає всі тести в поточному модулі. Прапорець -v показує назви окремих тестів та їх статус pass/fail.

Табличні тести: стандарт Go

Табличні тести зменшують дублювання і роблять додавання нових випадків тривіальним. Кожен рядок у таблиці представляє один сценарій з вхідними даними та очікуваними результатами. Цей патерн зустрічається в стандартній бібліотеці Go та в продакшн кодових базах по всій індустрії.

validator_test.gogo
package validator

import "testing"

func TestValidateEmail(t *testing.T) {
    tests := []struct {
        name    string
        email   string
        wantErr bool
    }{
        {"valid email", "user@example.com", false},
        {"missing at sign", "userexample.com", true},
        {"empty string", "", true},
        {"unicode local part", "user@例え.jp", false},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            err := ValidateEmail(tt.email)
            if (err != nil) != tt.wantErr {
                t.Errorf("ValidateEmail(%q) error = %v, wantErr %v",
                    tt.email, err, tt.wantErr)
            }
        })
    }
}

Метод t.Run створює підтести, які виконуються незалежно. Невдалі підтести повідомляють, який конкретний випадок провалився, не зупиняючи інші випадки.

Впровадження залежностей через інтерфейси

Інтерфейси Go дозволяють тестувати код, який залежить від зовнішніх систем: баз даних, HTTP клієнтів, файлових систем. Визначення мінімального інтерфейсу в місці використання дозволяє замінювати реальні імплементації на тестові дублери.

service.gogo
package order

// Repository defines the data access methods this service needs
type Repository interface {
    FindByID(id string) (*Order, error)
    Save(order *Order) error
}

// Service handles order business logic
type Service struct {
    repo Repository
}

// NewService creates a service with the given repository
func NewService(repo Repository) *Service {
    return &Service{repo: repo}
}

// Process validates and saves an order
func (s *Service) Process(order *Order) error {
    if order.Total <= 0 {
        return ErrInvalidTotal
    }
    return s.repo.Save(order)
}

Service приймає будь-який тип, що задовольняє Repository. Продакшн код передає імплементацію на основі бази даних. Тести передають мок або стаб.

Мокування з gomock та mockgen

Пакет gomock генерує мок-імплементації з визначень інтерфейсів. Команда mockgen читає вихідні файли і створює мок-структури, які записують виклики та повертають налаштовані значення.

bash
# Generate mocks for the Repository interface
mockgen -source=service.go -destination=mocks/repository_mock.go -package=mocks

Використання згенерованого моку в тестах:

service_test.gogo
package order

import (
    "testing"

    "github.com/stretchr/testify/assert"
    "go.uber.org/mock/gomock"

    "yourmodule/order/mocks"
)

func TestService_Process_SavesValidOrder(t *testing.T) {
    ctrl := gomock.NewController(t)
    mockRepo := mocks.NewMockRepository(ctrl)

    order := &Order{ID: "123", Total: 99.99}

    // Expect Save to be called once with this order, return nil
    mockRepo.EXPECT().
        Save(order).
        Return(nil).
        Times(1)

    svc := NewService(mockRepo)
    err := svc.Process(order)

    assert.NoError(t, err)
}

func TestService_Process_RejectsZeroTotal(t *testing.T) {
    ctrl := gomock.NewController(t)
    mockRepo := mocks.NewMockRepository(ctrl)

    // No calls expected to repo since validation fails first

    svc := NewService(mockRepo)
    err := svc.Process(&Order{ID: "456", Total: 0})

    assert.ErrorIs(t, err, ErrInvalidTotal)
}

Мок перевіряє, що Save була викликана рівно один раз з очікуваним аргументом. Якщо код, що тестується, ніколи не викликає Save або викликає її з іншими аргументами, тест провалюється.

Використання testify для assertions та test suites

testify надає функції assertions, які створюють чіткіші повідомлення про помилки, ніж ручні if-перевірки. Пакет assert продовжує виконання після помилок. Пакет require негайно зупиняє тест.

user_test.gogo
package user

import (
    "testing"

    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"
)

func TestParseUser(t *testing.T) {
    input := `{"id": "u1", "name": "Alice"}`

    user, err := ParseUser(input)

    require.NoError(t, err, "parsing should not fail")
    assert.Equal(t, "u1", user.ID)
    assert.Equal(t, "Alice", user.Name)
    assert.Empty(t, user.Email, "email should default to empty")
}

Виклик require.NoError зупиняє тест, якщо парсинг не вдається, запобігаючи панікам nil pointer у наступних assertions.

Готовий до співбесід з Go?

Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.

Тестування HTTP хендлерів з httptest

Пакет net/http/httptest надає ResponseRecorder для захоплення виводу хендлера та Server для запуску локального тестового сервера. Більшість модульних тестів використовують ResponseRecorder, щоб уникнути мережевого навантаження.

handler_test.gogo
package api

import (
    "net/http"
    "net/http/httptest"
    "testing"

    "github.com/stretchr/testify/assert"
)

func TestHealthHandler(t *testing.T) {
    req := httptest.NewRequest(http.MethodGet, "/health", nil)
    rec := httptest.NewRecorder()

    HealthHandler(rec, req)

    assert.Equal(t, http.StatusOK, rec.Code)
    assert.JSONEq(t, `{"status": "ok"}`, rec.Body.String())
}

Для хендлерів, що викликають зовнішні сервіси, впроваджуйте мок-клієнт або використовуйте httptest.NewServer для створення фейкового бекенду.

Поширені питання співбесід про тестування в Go

Інтерв'юери оцінюють знання тестування через питання, які розкривають досвід роботи з реальними кодовими базами. Ось патерни, що відрізняють підготовлених кандидатів:

"Як би ви протестували функцію, що викликає зовнішній API?"

Визначте інтерфейс для HTTP клієнта, впровадьте його у функцію або структуру та надайте мок, що повертає заздалегідь визначені відповіді. Це усуває мережеві виклики і робить тести детермінованими.

"Коли б ви використовували стаб замість моку?"

Стаби повертають заготовлені відповіді без перевірки того, як їх викликали. Моки перевіряють, що певні методи були викликані з певними аргументами. Використовуйте стаби, коли повернене значення важливіше за взаємодію. Використовуйте моки, коли перевірка взаємодії є метою тесту.

"Що таке табличні тести і чому Go їх надає перевагу?"

Табличні тести визначають тестові випадки як дані в slice структур. Вони зменшують boilerplate, роблять додавання випадків тривіальним і створюють читабельний тестовий вивід з t.Run. Патерн відповідає перевазі Go явного, повторюваного коду над хитрими абстракціями.

"Як ви обробляєте тестові фікстури або setup, що спільний для кількох тестів?"

Використовуйте TestMain для setup та teardown на рівні пакету. Використовуйте допоміжні функції або тестові набори (з testify) для спільного setup між пов'язаними тестами. Уникайте глобального стану, що створює зв'язок між тестами.

Готуєтесь до співбесіди з Go? Модуль тестування Go детально розглядає ці патерни з практичними питаннями.

Запуск тестів з покриттям та виявленням гонок

Команда go test приймає прапорці, що виявляють непротестовані шляхи коду та помилки конкурентності.

bash
# Run tests with coverage report
go test -cover ./...

# Generate HTML coverage visualization
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out -o coverage.html

# Detect race conditions (slower, but catches real bugs)
go test -race ./...

Детектор гонок інструментує доступ до пам'яті та позначає одночасні читання та записи до однієї змінної. Запуск тестів з -race в CI виловлює помилки, які проявляються лише при певному таймінгу.

Організація тестів у великих кодових базах

Зі зростанням проектів організація тестів впливає на підтримуваність:

  • Тести в тому ж пакеті (package foo) мають доступ до неекспортованих функцій та полів. Використовуйте для модульних тестів, що перевіряють внутрішню поведінку.
  • Тести у зовнішньому пакеті (package foo_test) імпортують пакет як зовнішній код. Використовуйте для інтеграційних тестів та для перевірки публічного API.
  • Каталоги testdata зберігають фікстури, такі як JSON файли, golden outputs або seed бази даних. Go ігнорує їх під час збірки.
  • Build tags відділяють модульні тести від інтеграційних тестів, що потребують зовнішніх систем. Запустіть go test -tags=integration ./..., щоб їх включити.

Більше про найкращі практики Go поза тестуванням можна знайти в статті про патерни конкурентності, яка охоплює горутини та канали.

Що запам'ятати про тестування в Go

  • Пакет testing поставляється з Go і не потребує зовнішніх залежностей
  • Табличні тести зменшують дублювання і є очікуваним патерном на співбесідах
  • Інтерфейси забезпечують впровадження залежностей; визначайте їх у місці використання, а не в імплементації
  • gomock генерує моки з інтерфейсів; testify надає чистіші assertions
  • httptest.ResponseRecorder захоплює вивід хендлера без мережевого навантаження
  • Прапорець -race виявляє помилки конкурентності, які модульні тести інакше пропускають
  • Метрики покриття спрямовують увагу, але не гарантують коректність

Починай практикувати!

Перевір свої знання з нашими симуляторами співбесід та технічними тестами.

Щоденний виклик

Чи знайдеш ти помилку в Go?

Справжній фрагмент коду, прихована помилка, одна спроба на день. Щоб спробувати, акаунт не потрібен.

Anthony Fillion-Maillet

Автор:

Anthony Fillion-Maillet

Засновник SharpSkill

Fullstack-розробник понад 10 років. Керує SharpSkill і відповідає за все, що тут публікується.

Оновлено 24 серпня 2026 р.

Теги

#go
#testing
#unit-tests
#mocks
#interview

Поділитися

Пов'язані статті