Какие подходы к тестированию в Go вы используете?

Ответ

В Go для тестирования используется встроенный пакет testing и стандартная утилита go test. Мой подход к тестированию включает несколько ключевых практик:

  1. Unit-тесты (Модульные тесты): Тестирование отдельных, изолированных частей кода (функций, методов). Это основа пирамиды тестирования.

  2. Table-driven тесты (Табличные тесты): Идеальный способ для проверки функции с множеством различных входных данных и ожидаемых результатов. Это делает тесты компактными и легко расширяемыми.

    func TestMultiply(t *testing.T) {
        testCases := []struct {
            name string
            a, b int
            want int
        }{
            {"positive numbers", 2, 3, 6},
            {"multiplication by zero", 0, 5, 0},
            {"negative number", -2, 4, -8},
        }
    
        for _, tc := range testCases {
            t.Run(tc.name, func(t *testing.T) {
                got := Multiply(tc.a, tc.b)
                if got != tc.want {
                    t.Errorf("Multiply(%d, %d) = %d; want %d", tc.a, tc.b, got, tc.want)
                }
            })
        }
    }
  3. Интеграционные тесты: Проверяют взаимодействие нескольких компонентов системы (например, сервис и база данных). Для их отделения от быстрых unit-тестов я использую билд-теги.

    • Файл теста помечается комментарием: //go:build integration
    • Запуск производится отдельной командой: go test -v -tags=integration
  4. Моки и Стабы (Mocks & Stubs): Для изоляции тестируемого компонента от его зависимостей (БД, внешние API) я использую моки. Это позволяет тестировать логику компонента, не затрагивая внешние системы. Популярные библиотеки: testify/mock и gomock.

  5. Бенчмарки (Benchmarks): Для измерения производительности критически важных участков кода. Используется testing.B.

    func BenchmarkMultiply(b *testing.B) {
        for i := 0; i < b.N; i++ {
            Multiply(1024, 1024)
        }
    }
  6. Вспомогательные библиотеки: Часто использую testify/assert и testify/require для более читаемых и удобных проверок вместо стандартных if got != want.

Ответ 18+ 🔞

Да ты послушай, как у нас в Go с тестами-то обстоят дела! Ну, то есть, вроде всё просто, пакет testing встроенный, go test под рукой, но если подойти с умом, то можно такую пирамиду выстроить, что любая новая фича будет как на ладони, а баги — ну, почти как мухи зимой, блядь.

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

А чтобы эти тычки палкой не превращались в трёхтомник «Война и мир», есть гениальная штука — table-driven тесты. Это ж просто песня, а не подход! Всю проверятельную хуйню в одну таблицу пишешь, и поехали.

func TestMultiply(t *testing.T) {
    testCases := []struct {
        name string
        a, b int
        want int
    }{
        {"positive numbers", 2, 3, 6},
        {"multiplication by zero", 0, 5, 0},
        {"negative number", -2, 4, -8},
    }

    for _, tc := range testCases {
        t.Run(tc.name, func(t *testing.T) {
            got := Multiply(tc.a, tc.b)
            if got != tc.want {
                t.Errorf("Multiply(%d, %d) = %d; want %d", tc.a, tc.b, got, tc.want)
            }
        })
    }
}

Видал? Красота! Добавил новую строку в таблицу — и новый тест готов, ёпта. Никакого копипаста, всё чисто.

Дальше — интеграционные тесты. Это когда твои модули уже друг с другом общаться начинают. Тут уже по-взрослому: база данных, внешние API, всякая такая срань. Чтобы они не тормозили все твои быстрые юнит-тесты, их надо отделять. Берёшь и пишешь в начале файла //go:build integration. И запускаешь их отдельной командой: go test -v -tags=integration. Как будто у тебя два разных войска: одно — быстрые десантники (юниты), а второе — тяжёлая артиллерия с обозом (интеграционные).

А теперь, внимание, самый цимес — моки, блядь! Ну представь, твой код звонит куда-то в облако, а облако сегодня тупит, или база данных легла. И что, из-за этого все твои тесты должны падать? Да хуй там! Мы создаём муляж, этакую мартышку-подставку, которая делает вид, что она — грозное внешнее API. Библиотеки testify/mock или gomock — это наши главные постановщики таких кукольных театров. Подсовываем компоненту поддельную зависимость и смотрим, как он с ней взаимодействует. Чистая магия, ебать мои старые костыли!

Ну и куда же без бенчмарков? Это когда уже не «работает ли», а «как быстро, сука, работает». Написал функцию, которая сортирует миллион чисел, а она делает это за час? Пиздец, не годится. Берёшь testing.B и начинаешь гонять.

func BenchmarkMultiply(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Multiply(1024, 1024)
    }
}

Запустил — и сразу видно, где бутылочное горлышко. Красота!

А, и ещё лайфхак: не мучайся со стандартными if got != want. Возьми библиотечку testify/assert — и твои проверки станут читаться как стихи, ей-богу. Вместо трёх строк кода — одна понятная строчка: assert.Equal(t, 42, result). Удобство — овердохуища!

Вот так-то, друг. Без всего этого — ты просто пишешь код в темноте. А с этим — у тебя как будто прожектор и инструкция по сборке одновременно.