如何使用 Go 中的测试包进行测试安装

当使用 测试包时,如何进行整体测试设置处理,为所有测试设置阶段?

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

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

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

// myfile_test.go
package main


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

Myfile _ test. init ()将在包 init ()函数之前调用。

Go 测试框架没有任何与 NUnit 的 安装属性等价的东西(在套件中的每个测试之前标记一个要调用的函数)。不过,还是有一些选择:

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

  2. 对 Go 的测试框架进行扩展,实现 xUnit 范例和概念:

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

通常,go 中的测试并不是以与其他语言相同的风格编写的。通常,测试函数相对较少,但是每个函数都包含一组表驱动的测试用例。参见 这篇文章是围棋小组的一个成员写的。

对于表驱动测试,您只需将任何设置代码放在执行表中指定的单个测试用例的循环之前,然后放置任何清理代码。

如果在测试函数之间仍然有共享的设置代码,那么可以将共享的设置代码提取到一个函数中,如果必须只执行一次的话,可以使用 sync.Once(或者像另一个答案所建议的那样,使用 init(),但是这样做的缺点是即使测试用例没有运行,设置也会完成(可能是因为使用 go test -run <regexp>限制了测试用例)

我想说的是,如果您认为需要在不同的测试之间进行共享设置,并且这些设置只执行一次,那么您应该思考一下是否真的需要它,以及表驱动测试是否不会更好。

从 Go 1.4开始,您可以实现 setup/teardown (无需在每次测试之前/之后复制函数)。总部部分概述了 给你的文件:

TestMain 在主要的 goroutine 中运行,可以执行任何安装和 拆卸是必要的在调用 m。运行。它应该然后调用 退出的结果。运行

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

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

一些其他的例子 可以在这里找到

在最新版本中,TestMain 特性被添加到 Go 的测试框架中 Release 是几个测试用例的简单解决方案 提供一个全局钩子来执行安装和关闭,控制 测试环境,在子进程中运行不同的代码,或者检查 对于由测试代码泄漏的资源,大多数包将不需要 TestMain,但对于那些需要它的时候,它是一个受欢迎的添加 需要。

给定一个单元测试的简单函数:

package math


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

您可以使用返回拆卸函数的 setup 函数对其进行测试。在调用 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
%

您可以使用这种方法传递一些额外的参数来设置/拆卸。

无耻的插头,我创建了 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{})
}

您可以使用一组不同的 setup/teardown 例程在包中创建您想要的任何测试组,每个测试组都可以创建。

如果有人正在寻找@Beforeeach (在测试文件中的每个测试之前运行)和@Aftereach (在测试文件中的每个测试之后运行)的替代方案,这里有一个 helper 代码片段。

func CreateForEach(setUp func(), tearDown func()) func(func()) {
return func(testFunc func()) {
setUp()
testFunc()
tearDown()
}
}


您可以在 TestMain 的帮助下像下面这样使用它

var RunTest = CreateForEach(setUp, tearDown)


func setUp() {
// SETUP METHOD WHICH IS REQUIRED TO RUN FOR EACH TEST METHOD
// your code here
}


func tearDown() {
// TEAR DOWN METHOD WHICH IS REQUIRED TO RUN FOR EACH TEST METHOD
// your code here
}


fun TestSample(t *testing.T) {
RunTest(func() {
// YOUR CODE HERE
})
}

你也可以检查: 在每个人之前

使用下面的模板,您可以在每个 TestMethod 中进行一行调用,同时执行设置和拆分。

func setupTest() func() {
// Setup code here


// tear down later
return func() {
// tear-down code here
}
}


func TestMethod(t *testing.T) {
defer setupTest()()
// asserts, ensures, requires... here
}

您可以使用 测试包来测试 设计-它将为所有测试设置阶段,而使用 拆除-它将在测试运行后清理阶段。

下面计算矩形的面积:

package main


import (
"errors"
"fmt"
)


func area(height float64, width float64) (float64, error) {
if height == width {
fmt.Printf("Rectangle with given dimension is a square. Area is: %f\n", height * width)
return height * width, nil
} else if height <= 0 || width <= 0 {
return 0, errors.New("Both dimensions need to be positive")
} else {
fmt.Printf("Area is: %f\n", height * width)
return height * width, nil
}
}


func main() {
var length float64  = 4.0
var breadth float64 = 5.0
area(length, breadth)
}

这是使用 TestMain作为 Salvador Dali 的 解释进行测试设置和拆卸的实现。(自从 v1.15以来,TestMain函数不再需要调用 os.Exit[ 裁判])

package main


import (
"log"
"testing"
)


var length float64
var breadth float64


func TestMain(m *testing.M) {
setup()
m.Run()
teardown()
}


func setup() {
length = 2.0
breadth = 3.0
log.Println("\n-----Setup complete-----")
}


func teardown() {
length = 0
breadth = 0
log.Println("\n----Teardown complete----")
}


func TestAreaOfRectangle(t *testing.T) {
val, err := area(length, breadth)
want := 6.0


if val != want && err != nil {
t.Errorf("Got %f and %v; Want %f and %v", val, err, want, nil)
}
}

这是使用子测试建立和拆卸测试的实现:

package main


import "testing"


func TestInvalidRectangle(t *testing.T) {
// setup
var length float64 = -2.0
var breadth float64 = 3.0
t.Log("\n-----Setup complete for invalid rectangle-----")


// sub-tests
t.Run("invalid dimensions return value", func(t *testing.T) {
val, _ := area(length, breadth)
area := 0.0


if val != area {
t.Errorf("Got %f; Want %f", val, area)
}
})


t.Run("invalid dimensions message", func(t *testing.T) {
_, err := area(length, breadth)
want := "Both dimensions need to be positive"


if err.Error() != want {
t.Errorf("Got error: %v; Want error: %v", err.Error(), want)
}
})


// teardown
t.Cleanup(func(){
length = 0
breadth = 0
t.Log("\n----Teardown complete for invalid rectangle----")
})
}


func TestRectangleIsSquare(t *testing.T) {
var length float64 = 3.0
var breadth float64 = 3.0
t.Log("\n-----Rectangle is square setup complete-----")


t.Run("valid dimensions value and message", func(t *testing.T) {
val, msg := area(length, breadth)
area := 9.0
if val != area && msg != nil {
t.Errorf("Got %f and %v; Want %f and %v", val, msg, area, nil)
}
})


t.Cleanup(func(){
length = 0
breadth = 0
t.Log("\n----Rectangle is square teardown Complete----")
})
}

下面是用于运行子测试的最小测试套件框架

  • BeforeAll,在测试中的所有子测试之前运行
  • BeforeEach,在每次分测试前运行
  • AfterEach,在每个分测试之后运行
  • AfterAll,在测试中运行所有子测试
package suit


import "testing"


func Of(subTests *SubTests) *SubTests {
if subTests.AfterAll != nil {
subTests.T.Cleanup(subTests.AfterAll)
}
return subTests
}


type SubTests struct {
T          *testing.T
BeforeEach func()
AfterEach  func()
AfterAll   func()
}


func (s *SubTests) TestIt(name string, f func(t *testing.T)) {
if s.AfterEach != nil {
defer s.AfterEach()
}
if s.BeforeEach != nil {
s.BeforeEach()
}
s.T.Run(name, f)
}

用法

func TestFoo(t *testing.T) {
// BeforeAll setup goes here


s := suit.Of(&suit.SubTests{
T:          t,
BeforeEach: func() { ... },
AfterEach:  func() { ... },
AfterAll:   func() { ... },
})


s.TestIt("returns true", func(t *testing.T) {
assert.Equal(t, 1, 1)
})
}