Подтвердить что ты не робот

Разделение модульных тестов и интеграционных тестов в Go

Существует ли наилучшая практика для разделения модульных тестов и тестов интеграции в GoLang (свидетельствовать)? У меня есть сочетание модульных тестов (которые не полагаются на какие-либо внешние ресурсы и, следовательно, работают очень быстро) и интеграционные тесты (которые полагаются на любые внешние ресурсы и тем самым работают медленнее). Итак, я хочу иметь возможность контролировать, включать ли интеграционные тесты, когда я говорю go test.

Наиболее простой метод, казалось бы, заключается в определении флага -integrate в main:

var runIntegrationTests = flag.Bool("integration", false
    , "Run the integration tests (in addition to the unit tests)")

И затем добавить инструкцию if в начало каждого теста интеграции:

if !*runIntegrationTests {
    this.T().Skip("To run this test, use: go test -integration")
}

Это лучшее, что я могу сделать? Я искал документацию для показа, чтобы узнать, существует ли, возможно, соглашение об именах или что-то, что это делает для меня, но ничего не нашел. Я что-то пропустил?

4b9b3361

Ответ 1

@Ainar-G предлагает несколько отличных шаблонов для разделения тестов.

Этот набор методов Go из SoundCloud рекомендует использовать теги сборки (описанные в разделе "Строить ограничения" в сборке пакет), чтобы выбрать, какие тесты следует запускать:

Напишите Integration_test.go и дайте ему тег построения интеграции. Определите (глобальные) флаги для таких вещей, как служебные адреса и соединения строк, и используйте их в своих тестах.

// +build integration

var fooAddr = flag.String(...)

func TestToo(t *testing.T) {
    f, err := foo.Connect(*fooAddr)
    // ...
}
Тест

go принимает теги сборки, подобные go build, поэтому вы можете вызвать go test -tags=integration. Он также синтезирует основной пакет, который вызывает flag.Parse, поэтому любые объявленные и видимые флаги будут обрабатываться и доступны для ваших тестов.

В качестве аналогичного варианта вы также можете выполнить тесты интеграции по умолчанию, используя условие построения // +build !unit, а затем отключите их по запросу, запустив go test -tags=unit

Ответ 2

Я вижу три возможных решения. Первый - использовать короткий режим для модульных тестов. Таким образом, вы использовали бы go test -short с модульными тестами и те же, но без флага -short, чтобы запустить ваши интеграционные тесты. Стандартная библиотека использует короткий режим, чтобы либо пропускать длительные тесты, либо заставлять их работать быстрее, предоставляя более простые данные.

Вторым является использование соглашения и вызов ваших тестов либо TestUnitFoo, либо TestIntegrationFoo, а затем используйте -run флаг тестирования, чтобы обозначить, какие тесты для запустить. Таким образом, вы должны использовать go test -run 'Unit' для модульных тестов и go test -run 'Integration' для тестов интеграции.

Третий вариант - использовать переменную окружения и получить ее в настройках тестов с помощью os.Getenv. Затем вы должны использовать простые go test для модульных тестов и FOO_TEST_INTEGRATION=true go test для тестов интеграции.

Я лично предпочел бы решение -short, так как оно проще и используется в стандартной библиотеке, поэтому он, по-видимому, является де-факто способом разделения/упрощения длительных тестов. Но решения -run и os.Getenv предлагают большую гибкость (требуется также более осторожно, поскольку регулярные выражения связаны с -run).

Ответ 3

Чтобы подробно описать мой комментарий к замечательному ответу @Ainar-G, за последний год я использовал комбинацию -short с соглашением об именах Integration для достижения наилучшего из обоих миров.

Единица измерения и интеграция проверяют гармонию в том же файле

Флаги сборки ранее заставляли меня иметь несколько файлов (services_test.go, services_integration_test.go и т.д.).

Вместо этого возьмите этот пример ниже, где первые два являются модульными тестами, и у меня есть тест интеграции в конце:

package services

import "testing"

func TestServiceFunc(t *testing.T) {
    t.Parallel()
    ...
}

func TestInvalidServiceFunc3(t *testing.T) {
    t.Parallel()
    ...
}

func TestPostgresVersionIntegration(t *testing.T) {
    if testing.Short() {
        t.Skip("skipping integration test")
    }
    ...
}

Обратите внимание, что в последнем тесте есть соглашение:

  • используя Integration в имени теста.
  • проверка, если выполняется под -short директивой флага.

В принципе, спецификация идет: "Обычно пишите все тесты, если это длительные тесты или тест интеграции, следуйте этому соглашению об именах и проверьте, чтобы -short был приятным для ваших сверстников".

Выполнять только Единичные тесты:

go test -v -short

это дает вам приятный набор сообщений, например:

=== RUN   TestPostgresVersionIntegration
--- SKIP: TestPostgresVersionIntegration (0.00s)
        service_test.go:138: skipping integration test

Только теги интеграции только:

go test -run Integration

Выполняются только тесты интеграции. Полезно для дымообразующих канарейков в производстве.

Очевидно, недостатком этого подхода является то, что если кто-то запускает go test, без флага -short, он будет по умолчанию запускать все тесты - тесты на единицу и интеграцию.

В действительности, если ваш проект достаточно велик для проведения тестов на единицу и интеграцию, вы, скорее всего, используете Makefile, где вы можете иметь простые директивы для использования go test -short в нем. Или просто поместите его в свой README.md файл и назовите его днем.

Ответ 4

Пример из SoundCloud docs выше имеет несчастливое свойство, которое "теги" интерпретируется как тег сборки и не будет передаваться тестовый код. Используйте другое имя флага, если вы хотите следовать этому подходу. Мне потребовалось немного, чтобы понять, что cmd/go/{build, testflag}.go определяет и интерпретирует flags.

Флаги

go test определены в: