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

98

Есть ли устоявшаяся передовая практика разделения модульных и интеграционных тестов в 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")
}

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

Крейг Джонс
источник
2
Я думаю, что stdlib использует -short для отключения тестов, которые попадают в сеть (и других длительных вещей). В остальном ваше решение выглядит нормально.
Volker
-short - хороший вариант, как и ваши собственные флаги сборки, но ваши флаги не обязательно должны быть в main. если вы определите var integration = flag.Bool("integration", true, "Enable integration testing.")переменную как вне функции, переменная будет отображаться в области пакета, и флаг будет работать правильно
Atifm

Ответы:

156

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

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

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

// +build integration

var fooAddr = flag.String(...)

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

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

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

@adamc комментарии:

Для всех, кто пытается использовать теги сборки, важно, чтобы // +build testкомментарий был первой строкой в ​​вашем файле, и чтобы вы включили пустую строку после комментария, в противном случае-tags команда проигнорирует директиву.

Кроме того, тег, используемый в комментарии к сборке, не может содержать тире, хотя знаки подчеркивания разрешены. Например, // +build unit-testsработать не будет, а // +build unit_testsбудет.

Alex
источник
1
Я использую это уже некоторое время, и это, безусловно, наиболее логичный и простой подход.
Ory Band
1
если у вас есть модульные тесты в одном пакете, вам нужно установить // + build unitмодульные тесты и использовать -tag unit для запуска тестов
LeoCBS
2
@ Tyler.z.yang не могли бы вы предоставить ссылку или более подробную информацию об устаревании тегов? Я не нашел такой информации. Я использую теги с go1.8 для способа, описанного в ответе, а также для имитации типов и функций в тестах. Думаю, это хорошая альтернатива интерфейсам.
Графов Александр Иванович
2
Для всех, кто пытается использовать теги сборки, важно, чтобы // +buildтестовый комментарий был первой строкой в ​​вашем файле, а вы добавили пустую строку после комментария, иначе -tagsкоманда проигнорирует директиву. Кроме того, тег, используемый в комментарии к сборке, не может содержать тире, хотя знаки подчеркивания разрешены. Например, // +build unit-testsне будет работать, тогда как // +build unit_testsбудет
adamc
6
Как работать с подстановочными знаками? go test -tags=integration ./...не работает, игнорирует тег
Erika Dsouza
54

Чтобы подробнее рассказать о моем комментарии к отличному ответу @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")
    }
    ...
}

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

  1. используя Integrationв названии теста.
  2. проверка, работает ли под -shortдирективой flag.

По сути, спецификация гласит: «Пишите все тесты в обычном режиме. Если это длительные тесты или интеграционный тест, следуйте этому соглашению об именах и проверяйте, -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 флага, по умолчанию будут выполняться все тесты - модульные и интеграционные.

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

eduncan911
источник
3
люблю простоту
Джейкоб Стэнли
Вы создаете отдельный пакет для такого теста, чтобы получить доступ только к публичным частям пакета? Или все смешано?
Dr.eel
@ Dr.eel Ну, это ОТ из ответа. Но лично я предпочитаю и то, и другое: другое имя пакета для тестов, чтобы я мог importсвой пакет и тестировать с ним, что в итоге показывает мне, как мой API выглядит для других. Затем я отслеживаю оставшуюся логику, которую необходимо включить в имена внутренних тестовых пакетов.
eduncan911
@ eduncan911 Спасибо за ответ! Насколько я понимаю, здесь package servicesсодержится тестовый набор интеграции, поэтому для тестирования API-интерфейса пакета как черного ящика мы должны назвать его по-другому, так package services_integration_testкак это не даст нам возможности работать с внутренними структурами. Итак, пакет для модульных тестов (доступ к внутреннему устройству) должен быть назван package services. Это так?
Dr.eel
Правильно, да. Вот чистый пример того, как я это делаю: github.com/eduncan911/podcast (обратите внимание на 100% покрытие кода, используя Примеры)
eduncan911 01
50

Я вижу три возможных решения. Первый - использовать короткий режим для модульных тестов. Таким образом, вы должны использовать 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).

Айнар-Г
источник
1
обратите внимание, что участники тестирования сообщества (например, Tester-Goобщие для IDE (Atom, Sublime и т. д.) имеют встроенную опцию для запуска с -shortфлагом, а также с -coverageдругими. поэтому я использую комбинацию Integration в названии теста вместе с if testing.Short()проверками внутри этих тестов. он позволяет мне использовать лучшее из обоих миров: запускать -shortв среде IDE и явно запускать только интеграционные тесты сgo test -run "Integration"
eduncan911
5

Недавно я пытался найти решение для того же самого. Это были мои критерии:

  • Решение должно быть универсальным
  • Нет отдельного пакета для интеграционных тестов
  • Разделение должно быть полным (я смогу запускать только интеграционные тесты )
  • Нет специального соглашения об именах для интеграционных тестов
  • Он должен работать без дополнительных инструментов

Вышеупомянутые решения (настраиваемый флаг, настраиваемый тег сборки, переменные среды) на самом деле не удовлетворяли всем вышеперечисленным критериям, поэтому, немного покопавшись и поиграв, я пришел к следующему решению:

package main

import (
    "flag"
    "regexp"
    "testing"
)

func TestIntegration(t *testing.T) {
    if m := flag.Lookup("test.run").Value.String(); m == "" || !regexp.MustCompile(m).MatchString(t.Name()) {
        t.Skip("skipping as execution was not requested explicitly using go test -run")
    }

    t.Parallel()

    t.Run("HelloWorld", testHelloWorld)
    t.Run("SayHello", testSayHello)
}

Реализация проста и минимальна. Хотя это требует простого соглашения для тестов, но менее подвержено ошибкам. Дальнейшее улучшение может заключаться в экспорте кода во вспомогательную функцию.

использование

Запускайте интеграционные тесты только для всех пакетов в проекте:

go test -v ./... -run ^TestIntegration$

Запустить все тесты ( обычные и интеграционные):

go test -v ./... -run .\*

Запускайте только обычные тесты:

go test -v ./...

Это решение хорошо работает без инструментов, но Makefile или некоторые псевдонимы могут облегчить пользователю задачу. Его также можно легко интегрировать в любую среду IDE, которая поддерживает запуск тестов go.

Полный пример можно найти здесь: https://github.com/sagikazarmark/modern-go-application

mark.sagikazar
источник