如何使用Go中的测试包进行测试设置


111

使用测试包时,如何进行整个测试设置过程,为所有测试奠定基础?

例如,在Nunit中有一个[SetUp]属性。

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

2
从1.4开始,您可以进行全局设置和拆卸
Salvador Dali

Answers:


159

从Go 1.4开始,您可以实施设置/拆卸(无需在每次测试之前/之后复制功能)。该文档概括这里主要部分:

TestMain在主goroutine中运行,并且可以在调用m.Run周围进行任何必要的设置和拆卸。然后,应使用m.Run的结果调用os.Exit。

我花了一些时间弄清楚,这意味着如果测试包含一个函数,func TestMain(m *testing.M)则将调用此函数而不是运行测试。在此函数中,我可以定义测试的运行方式。例如,我可以实现全局设置和拆卸:

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

可以在此处找到其他两个示例。

最新版本中添加到Go测试框架中的TestMain功能是针对多个测试用例的简单解决方案。TestMain提供了一个全局挂钩,以执行设置和关闭,控制测试环境,在子进程中运行不同的代码,或检查测试代码泄漏的资源。大多数软件包都不需要TestMain,但是在需要的时候,它是一个受欢迎的补充。


17
TestMain曾经放在包装中,所以它没什么用。我发现子测试对更复杂的目的更好。
伊南克·古姆斯

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()”是运行其他TestFunction的代码!
Alex Punnen

49

这可以通过init()_test.go文件中放置一个函数来实现。这将在init()功能之前运行。

// package_test.go
package main

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

_test.init()将在包init()函数之前调用。


2
我知道您在回答自己的问题,因此这可能满足您自己的用例,但这并不等同于您在问题中包含的NUnit示例。
詹姆斯·亨斯特里奇

@james,我对解决问题的方式显示了一种想法,其他人已经提供了一些很好的见解,包括您的见解。它对于获得外界影响来调整方法很有用。谢谢。
miltonb

2
很公平。您在此答案中显示的内容更接近于使用NUnit的[TestFixtureSetUp]属性。
詹姆斯·亨斯特里奇

2
它不包括拆卸部分
Taras Matsyk '18

7
如果您的测试文件与主要功能位于同一软件包中,则这不是一个好的解决方案。
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测试工具将在Shell控制台中报告日志记录语句:

% 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中的厄运金字塔)。并且,套件不会像外部测试那样自动运行测试。
伊南克·古姆斯

12

通常,进行中的测试与其他语言的编写风格不同。通常,测试功能相对较少,但每个功能都包含一组表驱动的测试用例。请参阅由Go团队之一撰写的本文。

对于表驱动的测试,您只需将所有设置代码放在执行表中指定的各个测试用例的循环之前,然后将所有清除代码放在后面。

如果您在测试功能之间仍然有共享的设置代码,则可以将共享的设置代码提取到函数中,并使用一次(sync.Once如果很重要的话)请确保它仅执行一次(或者如另一个答案所示,使用use)init(),但是这样做的缺点是设置即使未运行测试用例也将完成(可能是因为您已使用限制了测试用例go test -run <regexp>)。

我想说的是,如果您认为需要在不同的测试之间共享设置,这些设置将在一次执行后就确定您是否真的需要它,以及表驱动测试是否会更好。


6
当测试琐碎的事情(例如标志解析器或搅动数字的算法)时,这很棒。但是,当尝试测试所有需要相似样板代码的功能时,它并没有真正的帮助。我想我可以在一个数组中定义测试函数并对其进行迭代,但是实际上它并不是表驱动的,它实际上应该是一个内置在测试框架本身中的简单循环(以适当的测试套件的形式) (具有设置/拆卸功能)
iamtheddrman

9

Go测试框架没有与NUnit的SetUp属性等效的功能(在套件中的每个测试之前标记要调用的函数)。但是有一些选择:

  1. 只需SetUp在需要的地方通过每个测试调用函数。

  2. 使用Go的测试框架扩展来实现xUnit范例和概念。我想到了三个强有力的选择:

这些库中的每一个都鼓励您将测试组织到类似于其他xUnit框架的套件/夹具中,并会在每种Test*方法之前调用套件/夹具类型上的setup 方法。


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{})
}

您可以使用一组不同的设置/拆卸例程,在一个包中随心所欲地创建任何测试组。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.