Як я можу виконати налаштування тесту за допомогою тестового пакету в Go


111

Як я можу зробити загальну обробку тестових налаштувань, яка встановлює основу для всіх тестів при використанні пакета тестування ?

Як приклад в Nunit є [SetUp]атрибут.

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

Відповіді:


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 для офіційної документації
de-jcup

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

1
зауважте, що 'code: = m.Run ()' - це той, який запускає інші TestFunctions!
Алекс Пуннен

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
Це не гарне рішення, якщо ваш тестовий файл знаходиться в одному пакеті з основною функцією.
Мишка шукала

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.

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

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

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


6
Це чудово під час тестування дрібницьких речей, таких як аналізатор прапора або алгоритм, який визначає числа. Але це не дуже допомагає при тестуванні різноманітних функціональних можливостей, які вимагають аналогічного кодового коду. Я припускаю, що я міг би визначити свої тестові функції в масиві і повторити їх, але тоді це насправді не керовано таблицею настільки, як простий цикл, який дійсно повинен бути просто вбудований у саму рамку тестування (у вигляді належного тестового набору з функціями налаштування /
відмовлення

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.