英文:
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属性相当的东西(标记一个函数在测试套件中的每个测试之前被调用)。不过有几个选项:
-
可以在需要的每个测试中直接调用你的
SetUp
函数。 -
使用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:
-
Simply call your
SetUp
function from each test where it is needed. -
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 <regexp>
.)
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 (
"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)
}
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 (
"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)
}
}
And this is the implementation for test setup and teardown using sub-tests:
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----")
})
}
答案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 (
"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{})
}
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 testBeforeEach
, ran before each subtestAfterEach
, ran after each subtestAfterAll
, ran after all subtests in a test
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)
}
Usage
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)
})
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论