Розділювальні модульні тести та інтеграційні тести в Go


97

Чи існує встановлена ​​найкраща практика розділення модульних тестів та інтеграційних тестів у GoLang (засвідчити)? У мене є поєднання модульних тестів (які не покладаються на будь-які зовнішні ресурси і, отже, працюють дуже швидко) та інтеграційних тестів (які покладаються на будь-які зовнішні ресурси і, отже, працюють повільніше). Отже, я хочу мати можливість контролювати, включати чи ні тести інтеграції, коли я кажу go test.

Здається, найбільш прямим методом є визначення -інтегруючого прапора в основному:

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 - хороший варіант, як і ваші власні прапори збірки, але ваші прапори не повинні бути головними. якщо ви визначите var як var integration = flag.Bool("integration", true, "Enable integration testing.")поза функцією, змінна відображатиметься в обсязі пакета, а прапор працюватиме належним чином
Atifm

Відповіді:


155

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

Цей набір практик Go від SoundCloud рекомендує використовувати теги збірки ( описані в розділі "Обмеження збірки" пакету збірки ) для вибору тестів для запуску:

Напишіть інтеграційний_тест.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.

Коментарі @adamc:

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

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


1
Я використовую це вже деякий час, і це, безумовно, найбільш логічний і простий підхід.
Ory Band

1
якщо у вас є // + build unitмодульні тести в одному пакеті, вам потрібно встановити в модульних тестах і використовувати модуль -tag для запуску тестів
LeoCBS

2
@ Tyler.z.yang, чи можете ви надати посилання або докладнішу інформацію про припинення тегів? Я не знайшов такої інформації. Я використовую теги з go1.8 для способу, описаного у відповіді, а також для глузування типів та функцій у тестах. Це гарна альтернатива інтерфейсам.
Олександр Іванович Графов

2
Для всіх, хто намагається використовувати теги побудови, важливо, щоб // +buildтестовий коментар був першим рядком у вашому файлі, і щоб ви додали порожній рядок після коментаря, інакше -tagsкоманда буде ігнорувати директиву. Крім того, тег, використаний у коментарі до збірки, не може мати тире, хоча допускається підкреслення. Наприклад, // +build unit-testsне буде працювати, тоді як // +build unit_testsбуде
adamc

6
Як обробляти символи підстановки? go test -tags=integration ./...не працює, він ігнорує тег
Еріка Суза

53

Щоб детально розказати про свій коментар до чудової відповіді @ Ainar-G, протягом останнього року я використовував поєднання -shortз Integrationконвенцією іменування, щоб досягти найкращого з обох світів.

Unit і 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директивою прапора.

В основному, специфікація говорить: "пишіть усі тести нормально. Якщо це довготривалі тести або інтеграційні тести, дотримуйтесь цієї угоди про іменування та перевірте, -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файл і назвіть день.


3
люблю простоту
Джейкоб Стенлі

Ви створюєте окремий пакет для такого тесту для доступу лише до загальнодоступних частин пакету? Або все змішане?
Dr.eel

@ Dr.eel Ну, це відповідь із відповіді. Але особисто я віддаю перевагу обом: інша назва пакета для тестів, щоб я міг importмістити свій пакет і тестувати його, що в підсумку показує мені, як виглядає мій API для інших. Потім я продовжую роботу з будь-якою логікою, що залишилася, яку потрібно охопити як імена внутрішніх тестових пакетів.
eduncan911

@ eduncan911 Дякую за відповідь! Отож, як я розумію, тут package servicesміститься тест для інтеграції, тому для тестування пакету APIfo як чорного ящика ми повинні назвати його по-іншому, package services_integration_testце не дасть нам можливості працювати з внутрішніми структурами. Отже, слід назвати пакет для модульних тестів (доступ до внутрішніх елементів) package services. Чи це так?
Dr.eel

Це правильно, так. Ось чистий приклад того, як я це роблю: github.com/eduncan911/podcast (зверніть увагу на 100% охоплення коду, використовуючи приклади)
eduncan911,

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рішенню, оскільки воно простіше і використовується в стандартній бібліотеці, тому, схоже, це фактичний спосіб розділення / спрощення тривалих тестів. Але рішення -runand та os.Getenvпропонують більшу гнучкість (також потрібна більша обережність, оскільки задіяні регулярні вирази -run).


1
зауважте, що Tester-Goспільні для середовищ розроблення середовища (Atom, Sublime та ін.) спільні для середовищ для запуску тестові програми спільноти мають вбудовану функцію для запуску з -shortпрапором, а також -coverageінші. тому я використовую комбінацію обох інтеграцій в назві тесту, разом із 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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.