Как выполнить настройку теста с помощью пакета тестирования в Go


111

Как я могу выполнить общую обработку настройки теста, которая создает основу для всех тестов при использовании пакета тестирования ?

В качестве примера в Nunit есть [SetUp]атрибут.

[TestFixture]
public class SuccessTests
{
  [SetUp] public void Init()
  { /* Load test data */ }
}

2
Начиная с 1.4, вы можете иметь глобальную настройку и разборку
Сальвадор Дали

Ответы:


159

Начиная с Go 1.4, вы можете реализовать настройку / разборку (нет необходимости копировать свои функции до / после каждого теста). Документация изложена здесь в главном разделе:

TestMain запускается в основной горутине и может выполнять любую настройку и разборку, необходимую для вызова m.Run. Затем он должен вызвать os.Exit с результатом m.Run

Мне потребовалось некоторое время, чтобы понять, что это означает, что если тест содержит функцию, func TestMain(m *testing.M)то эта функция будет вызываться вместо запуска теста. И в этой функции я могу определить, как будут выполняться тесты. Например, я могу реализовать глобальную настройку и демонтаж:

func TestMain(m *testing.M) {
    setup()
    code := m.Run() 
    shutdown()
    os.Exit(code)
}

Здесь можно найти еще пару примеров .

Функция TestMain, добавленная в среду тестирования Go в последней версии, представляет собой простое решение для нескольких сценариев использования тестирования. TestMain предоставляет глобальную ловушку для выполнения настройки и завершения работы, управления средой тестирования, запуска другого кода в дочернем процессе или проверки ресурсов, утечка которых выполняется тестовым кодом. Большинству пакетов не требуется TestMain, но это долгожданное дополнение в те времена, когда это необходимо.


17
TestMainодин раз в пакете, поэтому он не так полезен. Я считаю, что субтесты лучше подходят для более сложных целей.
Inanc Gumus

3
Как вы должны передавать контекст из функции настройки в тесты без использования глобальных переменных? Например, если mySetupFunction () создает временный каталог для выполнения тестирования (с уникальным случайным именем), как тесты узнают имя каталога? Должно быть место для установки этого контекста ??
Lqueryvg

1
Кажется , что это официальный способ обработки до и после того, как крючки для испытаний см golang.org/pkg/testing/#hdr-Main для официальной документации
де-jcup

4
@InancGumuslstat $GOROOT/subtests: no such file or directory
030,

1
обратите внимание, что 'code: = m.Run ()' - это тот, который запускает другие TestFunctions!
Alex Punnen 08

49

Этого можно добиться, поместив init()функцию в _test.goфайл. Это будет запущено перед init()функцией.

// package_test.go
package main

func init() {
     /* load test data */
}

_Test.init () будет вызываться перед функцией пакета init ().


2
Я знаю, что вы отвечаете на свой вопрос, так что это, вероятно, соответствует вашему собственному варианту использования, но это не эквивалентно примеру NUnit, который вы включили в свой вопрос.
Джеймс Хенстридж

Что ж, @james, я показал одну мысль о том, как ответить на этот вопрос, а другие уже предоставили некоторые хорошие идеи, включая вашу. Полезно получить внешние влияния, чтобы настроить свой подход. Спасибо.
miltonb

2
Справедливо. То, что вы показали в этом ответе, несколько ближе к использованию [TestFixtureSetUp]вместо этого атрибута NUnit .
Джеймс Хенстридж

2
это не включает
демонтажную

7
Это не лучшее решение, если ваш тестовый файл находится в одном пакете с функцией main.
MouseWanted

28

Учитывая простую функцию для модульного тестирования:

package math

func Sum(a, b int) int {
    return a + b
}

Вы можете проверить это с помощью функции настройки, которая возвращает функцию разрыва. А после вызова setup () вы можете сделать отложенный вызов teardown ().

package math

import "testing"

func setupTestCase(t *testing.T) func(t *testing.T) {
    t.Log("setup test case")
    return func(t *testing.T) {
        t.Log("teardown test case")
    }
}

func setupSubTest(t *testing.T) func(t *testing.T) {
    t.Log("setup sub test")
    return func(t *testing.T) {
        t.Log("teardown sub test")
    }
}

func TestAddition(t *testing.T) {
    cases := []struct {
        name     string
        a        int
        b        int
        expected int
    }{
        {"add", 2, 2, 4},
        {"minus", 0, -2, -2},
        {"zero", 0, 0, 0},
    }

    teardownTestCase := setupTestCase(t)
    defer teardownTestCase(t)

    for _, tc := range cases {
        t.Run(tc.name, func(t *testing.T) {
            teardownSubTest := setupSubTest(t)
            defer teardownSubTest(t)

            result := Sum(tc.a, tc.b)
            if result != tc.expected {
                t.Fatalf("expected sum %v, but got %v", tc.expected, result)
            }
        })
    }
}

Инструмент тестирования Go сообщит операторы регистрации в консоли оболочки:

% go test -v
=== RUN   TestAddition
=== RUN   TestAddition/add
=== RUN   TestAddition/minus
=== RUN   TestAddition/zero
--- PASS: TestAddition (0.00s)
    math_test.go:6: setup test case
    --- PASS: TestAddition/add (0.00s)
        math_test.go:13: setup sub test
        math_test.go:15: teardown sub test
    --- PASS: TestAddition/minus (0.00s)
        math_test.go:13: setup sub test
        math_test.go:15: teardown sub test
    --- PASS: TestAddition/zero (0.00s)
        math_test.go:13: setup sub test
        math_test.go:15: teardown sub test
    math_test.go:8: teardown test case
PASS
ok      github.com/kare/go-unit-test-setup-teardown 0.010s
% 

При таком подходе вы можете передать некоторые дополнительные параметры для настройки / разборки.


2
Это действительно простой, но эффективный трюк. Отличное использование синтаксиса Go.
miltonb

1
Да, но это увеличивает вложенность ( своего рода пирамида гибели в javascript ). И тесты не запускаются автоматически набором, как во внешних тестах.
Inanc Gumus

12

Обычно тесты в go не написаны в том же стиле, что и другие языки. Часто тестовых функций относительно меньше, но каждая содержит набор тестовых примеров на основе таблиц. См. Эту статью, написанную одним из разработчиков Go.

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

Если у вас все еще есть общий код настройки для тестовых функций, вы можете извлечь общий код настройки в функцию и использовать, sync.Onceесли важно, чтобы он выполнялся ровно один раз (или, как предлагает другой ответ, используйте init(), но это имеет тот недостаток, что настройка будет выполняться, даже если тестовые примеры не запускаются (возможно, потому, что вы ограничили тестовые примеры с помощью go test -run <regexp>.)

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


7
Это замечательно при тестировании таких тривиальных вещей, как парсер флагов или алгоритм, обрабатывающий числа. Но это не очень помогает при попытке протестировать различные части функциональности, для которых требуется одинаковый шаблонный код. Полагаю, я мог бы определить свои тестовые функции в массиве и перебирать их, но тогда это не столько управляемый таблицами, сколько простой цикл, который на самом деле должен быть просто встроен в саму среду тестирования (в виде надлежащего набора тестов с функциями настройки / разборки)
iamtheddrman

9

Платформа тестирования Go не имеет ничего эквивалентного атрибуту SetUp NUnit (помечающему функцию, которая должна быть вызвана перед каждым тестом в наборе). Однако есть несколько вариантов:

  1. Просто вызовите свою SetUpфункцию из каждого теста, где это необходимо.

  2. Используйте расширение для инфраструктуры тестирования Go, которое реализует парадигмы и концепции xUnit. На ум приходят три сильных варианта:

Каждая из этих библиотек побуждает вас организовывать ваши тесты в наборы / приспособления, аналогичные другим фреймворкам xUnit, и будет вызывать методы настройки для типа набора / приспособления перед каждым из Test*методов.


0

Бесстыдный плагин, я создал https://github.com/houqp/gtest, чтобы помочь решить именно эту проблему.

Вот краткий пример:

import (
  "strings"
  "testing"
  "github.com/houqp/gtest"
)

type SampleTests struct{}

// Setup and Teardown are invoked per test group run
func (s *SampleTests) Setup(t *testing.T)      {}
func (s *SampleTests) Teardown(t *testing.T)   {}
// BeforeEach and AfterEach are invoked per test run
func (s *SampleTests) BeforeEach(t *testing.T) {}
func (s *SampleTests) AfterEach(t *testing.T)  {}

func (s *SampleTests) SubTestCompare(t *testing.T) {
  if 1 != 1 {
    t.FailNow()
  }
}

func (s *SampleTests) SubTestCheckPrefix(t *testing.T) {
  if !strings.HasPrefix("abc", "ab") {
    t.FailNow()
  }
}

func TestSampleTests(t *testing.T) {
  gtest.RunSubTests(t, &SampleTests{})
}

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.