你可以使用Go语言中的testing包来进行测试设置。

huangapple go评论96阅读模式
英文:

How can I do test setup using the testing package in Go

问题

使用testing包时,如何进行整体测试设置处理,为所有测试做准备?

以NUnit为例,可以使用[SetUp]属性。

[TestFixture]
public class SuccessTests
{
  [SetUp] public void Init()
  { /* 加载测试数据 */ }
}
英文:

How can I do overall test setup processing which sets the stage for all the tests when using the testing package?

As an example in Nunit there is a [SetUp] attribute.

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

答案1

得分: 269

从Go 1.4开始,你可以实现设置/拆卸(无需在每个测试之前/之后复制函数)。文档在Main部分中概述了这一点:

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,但在需要时它是一个受欢迎的补充。

英文:

Starting with Go 1.4 you can implement setup/teardown (no need to copy your functions before/after each test). The documentation is outlined here in the Main section:

> TestMain runs in the main goroutine and can do whatever setup and
> teardown is necessary around a call to m.Run. It should then call
> os.Exit with the result of m.Run

It took me some time to figure out that this means that if a test contains a function func TestMain(m *testing.M) then this function will be called instead of running the test. And in this function I can define how the tests will run. For example I can implement global setup and teardown:

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

A couple of other examples can be found here.

> The TestMain feature added to Go’s testing framework in the latest
> release is a simple solution for several testing use cases. TestMain
> provides a global hook to perform setup and shutdown, control the
> testing environment, run different code in a child process, or check
> for resources leaked by test code. Most packages will not need a
> TestMain, but it is a welcome addition for those times when it is
> needed.

答案2

得分: 79

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

// myfile_test.go
package main

func init() {
     /* 加载测试数据 */
}

myfile_test.init()将在包的init()函数之前被调用。

英文:

This can be achieved by putting a init() function in the myfile_test.go file. This will be run before the init() function.

// myfile_test.go
package main

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

The myfile_test.init() will be called before the package init() function.

答案3

得分: 54

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

package math

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

你可以使用一个返回teardown函数的setup函数来测试它。在调用setup()之后,你可以使用deferred调用来调用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
%

你可以使用这种方法向setup/teardown传递一些额外的参数。

英文:

Given a simple function to unit test:

package math

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

You can test it with a setup function that returns teardown function. And after calling setup() you can make a deferred call to 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 testing tool will report the logging statements in the shell console:

% 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
% 

You can pass some additional parameters to setup/teardown with this approach.

答案4

得分: 23

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

func setupTest() func() {
    // 设置代码在这里

    //稍后拆卸
    return func() {
        //拆卸代码在这里
    }
}

func TestMethod(t *testing.T) {
    defer setupTest()()
    // 在这里进行断言、确保、要求...
}

希望这对您有所帮助!

英文:

With the following template, you can make a one line call in each TestMethod that does both setup and tear-down.

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
}

答案5

得分: 15

Go的测试框架没有与NUnit的SetUp属性相当的东西(标记一个函数在测试套件中的每个测试之前被调用)。不过有几个选项:

  1. 可以在需要的每个测试中直接调用你的SetUp函数。

  2. 使用Go测试框架的扩展,实现xUnit的范例和概念。有三个强大的选项:

这些库都鼓励你将测试组织成类似其他xUnit框架的套件/夹具,并且会在每个Test*方法之前调用套件/夹具类型的设置方法。

英文:

The Go testing framework doesn't have anything equivalent to NUnit's SetUp attribute (marking a function to be called before each test in the suite). There are a few options though:

  1. Simply call your SetUp function from each test where it is needed.

  2. Use an extension to Go's testing framework that implements xUnit paradigms and concepts. Three strong options come to mind:

Each of these libraries encourage you to organize your tests into suites/fixtures similar to other xUnit frameworks, and will call the setup methods on the suite/fixture type before each of the Test* methods.

答案6

得分: 12

通常,Go语言中的测试与其他语言的写法不同。通常情况下,测试函数较少,但每个函数中包含了一组表驱动的测试用例。可以参考Go团队的一篇文章

使用表驱动测试时,你只需在执行表中指定的每个测试用例之前放置任何设置代码,并在之后放置任何清理代码。

如果你仍然在测试函数之间有共享的设置代码,你可以将共享的设置代码提取到一个函数中,并使用sync.Once来确保它只执行一次(或者根据另一个答案的建议,使用init(),但这样做的缺点是即使测试用例没有运行(可能是因为你使用了go test -run <regexp>来限制测试用例),设置也会被执行。

我认为,如果你认为你需要在不同的测试之间共享设置,并且需要确保它只执行一次,你应该考虑是否真的需要它,以及是否使用表驱动测试会更好。

英文:

Typically, tests in go aren't written in the same style as other languages. Often, there's relatively fewer test functions, but each contains a table-driven set of test cases. See this article written by one of the Go team.

With a table-driven test, you simply put any setup code before the loop that executes the individual test-cases specified in the table, and put any cleanup code afterwards.

If you still have shared setup code between test functions, you can extract the shared setup code into a function, and use a sync.Once if it's important that it's executed exactly once (or as another answer suggests, use init(), but this has the disadvantage that the setup will be done even if the test cases aren't run (perhaps because you've limited the test cases by using go test -run &lt;regexp&gt;.)

I'd say if you think you need shared setup between different tests that gets executed exactly once you should have a think if you really need it, and if a table-driven test wouldn't be better.

答案7

得分: 5

如果有人正在寻找@BeforeEach(在测试文件中每个测试之前运行)和@AfterEach(在测试文件中每个测试之后运行)的替代方法,可以使用以下辅助代码片段。

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

你可以通过TestMain来使用它,示例如下:

var RunTest = CreateForEach(setUp, tearDown)

func setUp() {
   // 每个测试方法运行前需要执行的设置方法
   // 在这里编写你的代码
}

func tearDown() {
  // 每个测试方法运行后需要执行的清理方法
  // 在这里编写你的代码
}

fun TestSample(t *testing.T) {
  RunTest(func() {
    // 在这里编写你的代码
  })
}

你也可以参考:go-beforeeach

英文:

In case someone is looking an alternative of @BeforeEach (which runs before each test in a test file) and @AfterEach (which runs after test in a test file), here's a helper snippet.

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

You can use it like below with help of 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
  })
}

also you can check: go-beforeeach

答案8

得分: 3

你可以使用testing包来进行测试的设置 - 它将为所有测试设置舞台,以及拆卸 - 它将在测试运行后清理舞台。

下面是计算矩形面积的代码:

package main

import (
	"errors"
	"fmt"
)

func area(height float64, width float64) (float64, error) {
	if height == width {
		fmt.Printf("给定尺寸的矩形是正方形。面积为:%f\n", height * width)
		return height * width, nil
	} else if height <= 0 || width <= 0 {
		return 0, errors.New("两个尺寸都需要是正数")
	} else {
		fmt.Printf("面积为:%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 [ref]):

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-----设置完成-----")
}

func teardown() {
	length = 0
	breadth = 0
	log.Println("\n----拆卸完成----")
}

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

	if val != want && err != nil {
		t.Errorf("得到 %f 和 %v;期望 %f 和 %v", val, err, want, nil)
	}
}

这是使用子测试进行测试设置和拆卸的实现:

package main

import "testing"

func TestInvalidRectangle(t *testing.T) {
	// 设置
    var length float64 = -2.0
	var breadth float64 = 3.0
	t.Log("\n-----设置完成,用于无效矩形-----")

	// 子测试
    t.Run("无效尺寸返回值", func(t *testing.T) {
    	val, _ := area(length, breadth)
    	area := 0.0

    	if val != area {
    		t.Errorf("得到 %f;期望 %f", val, area)
    	}
    })

    t.Run("无效尺寸消息", func(t *testing.T) {
    	_, err := area(length, breadth)
    	want := "两个尺寸都需要是正数"

    	if err.Error() != want {
			t.Errorf("得到错误:%v;期望错误:%v", err.Error(), want)
		}
    })

    // 拆卸
    t.Cleanup(func(){
        length = 0
        breadth = 0
        t.Log("\n----拆卸完成,用于无效矩形----")
    })
}

func TestRectangleIsSquare(t *testing.T) {
    var length float64 = 3.0
	var breadth float64 = 3.0
	t.Log("\n-----矩形是正方形设置完成-----")

    t.Run("有效尺寸值和消息", func(t *testing.T) {
    	val, msg := area(length, breadth)
    	area := 9.0
    	if val != area && msg != nil {
    		t.Errorf("得到 %f 和 %v;期望 %f 和 %v", val, msg, area, nil)
    	}
    })

    t.Cleanup(func(){
        length = 0
        breadth = 0
        t.Log("\n----矩形是正方形拆卸完成----")
    })
}
英文:

You can use the testing package for test setup - which will set the stage for all tests and teardown - which will cleanup the stage after tests have run.

The below calculates the area of a rectangle:

package main
import (
&quot;errors&quot;
&quot;fmt&quot;
)
func area(height float64, width float64) (float64, error) {
if height == width {
fmt.Printf(&quot;Rectangle with given dimension is a square. Area is: %f\n&quot;, height * width)
return height * width, nil
} else if height &lt;= 0 || width &lt;= 0 {
return 0, errors.New(&quot;Both dimensions need to be positive&quot;)
} else {
fmt.Printf(&quot;Area is: %f\n&quot;, height * width)
return height * width, nil
}
}
func main() {
var length float64  = 4.0
var breadth float64 = 5.0
area(length, breadth)
}

This is the implementation for test setup and teardown using TestMain as Salvador Dali's explains. (Note that since v1.15 the TestMain function is no longer required to call os.Exit [ref])

package main
import (
&quot;log&quot;
&quot;testing&quot;
)
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(&quot;\n-----Setup complete-----&quot;)
}
func teardown() {
length = 0
breadth = 0
log.Println(&quot;\n----Teardown complete----&quot;)
}
func TestAreaOfRectangle(t *testing.T) {
val, err := area(length, breadth)	
want := 6.0
if val != want &amp;&amp; err != nil {
t.Errorf(&quot;Got %f and %v; Want %f and %v&quot;, val, err, want, nil)
}
}

And this is the implementation for test setup and teardown using sub-tests:

package main
import &quot;testing&quot;
func TestInvalidRectangle(t *testing.T) {
// setup
var length float64 = -2.0
var breadth float64 = 3.0
t.Log(&quot;\n-----Setup complete for invalid rectangle-----&quot;)
// sub-tests
t.Run(&quot;invalid dimensions return value&quot;, func(t *testing.T) {
val, _ := area(length, breadth)
area := 0.0
if val != area {
t.Errorf(&quot;Got %f; Want %f&quot;, val, area)
}
})
t.Run(&quot;invalid dimensions message&quot;, func(t *testing.T) {
_, err := area(length, breadth)
want := &quot;Both dimensions need to be positive&quot;
if err.Error() != want {
t.Errorf(&quot;Got error: %v; Want error: %v&quot;, err.Error(), want)
}
})
// teardown
t.Cleanup(func(){
length = 0
breadth = 0
t.Log(&quot;\n----Teardown complete for invalid rectangle----&quot;)
})
}
func TestRectangleIsSquare(t *testing.T) {
var length float64 = 3.0
var breadth float64 = 3.0
t.Log(&quot;\n-----Rectangle is square setup complete-----&quot;)
t.Run(&quot;valid dimensions value and message&quot;, func(t *testing.T) {
val, msg := area(length, breadth)
area := 9.0
if val != area &amp;&amp; msg != nil {
t.Errorf(&quot;Got %f and %v; Want %f and %v&quot;, val, msg, area, nil)
}
})
t.Cleanup(func(){
length = 0
breadth = 0
t.Log(&quot;\n----Rectangle is square teardown Complete----&quot;)
})
}

答案9

得分: 2

无耻地插一下,我创建了https://github.com/houqp/gtest来帮助解决这个问题。

这里是一个快速示例:

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

type SampleTests struct{}

// 每个测试组运行前都会调用 Setup 和 Teardown
func (s *SampleTests) Setup(t *testing.T)      {}
func (s *SampleTests) Teardown(t *testing.T)   {}
// 每个测试运行前都会调用 BeforeEach 和 AfterEach
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{})
}

你可以在一个包中创建任意数量的测试组,每个测试组都可以使用不同的设置/拆卸例程。

英文:

Shameless plug, I created https://github.com/houqp/gtest to help solve exactly this problem.

Here is a quick example:

import (
  &quot;strings&quot;
  &quot;testing&quot;
  &quot;github.com/houqp/gtest&quot;
)

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(&quot;abc&quot;, &quot;ab&quot;) {
    t.FailNow()
  }
}

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

You can create as any test group you want within a package with each of them using a different set of setup/teardown routines.

答案10

得分: 0

这是一个最小的测试套件框架,用于运行子测试,其中包括以下功能:

  • 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 设置

	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)
	})
}
英文:

Here is the minimal test suit framework to run subtests with

  • BeforeAll, ran before all subtests in a test
  • BeforeEach, ran before each subtest
  • AfterEach, ran after each subtest
  • AfterAll, ran after all subtests in a test
package suit

import &quot;testing&quot;

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

Usage

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

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

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

huangapple
  • 本文由 发表于 2014年5月19日 12:36:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/23729790.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定