英文:
setup and teardown for each test using std testing package
问题
我正在使用go的"testing"包。像下面这样运行我的测试。
func TestMain(m *testing.M) {
// 设置
...
os.Exit(m.Run())
// 清理
}
这将在运行任何测试之前运行设置(setup),并在所有测试完成后运行清理(teardown)。我确实需要这样做,因为设置(setup)会设置数据库。但是,我还需要找到一种方法来运行每个测试的独立设置(setup)和清理(teardown)。对于我正在运行的单元测试,我希望在每个测试之前清除数据库,以确保数据库内容不会导致意外行为。
英文:
I am using go "testing" package. Running my tests like below.
func TestMain(m *testing.M) {
...
// Setup
os.Exit(m.Run())
// Teardown
}
This will run a setup before any test is run, and a teardown after all tests are complete. And I do need this, as the setup sets the DB up. But also, I need, and yet to find out a way to run a per-test setup/teardown. For the unit tests I am running, I would like to clear the DB before every test, so that there are no issues with the content of the DB causing unexpected behavior.
答案1
得分: 39
Go 1.14的更新(2020年第一季度)
现在,testing
包支持清理函数,可以在测试或基准测试完成后通过调用T.Cleanup
或B.Cleanup
来执行。例如,
func TestFunction(t *testing.T) {
// 设置代码
// 子测试
t.Run()
t.Run()
...
// 清理
t.Cleanup(func(){
// 拆卸代码
})
}
在这里,t.Cleanup在测试及其所有子测试完成后运行。
原始答案(2017年2月)
如Kare Nuorteva的文章“Go单元测试的设置和拆卸”所示,您可以使用一个设置函数来返回一个拆卸函数以供延迟使用。
参见这个gist:
func setupSubTest(t *testing.T) func(t *testing.T) {
t.Log("设置子测试")
return func(t *testing.T) {
t.Log("拆卸子测试")
}
}
设置函数负责定义并返回拆卸函数。
对于每个测试,例如在表驱动测试场景中:
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("期望的和为 %v,但得到了 %v", tc.expected, result)
}
})
}
英文:
Update for Go 1.14 (Q1 2020)
The testing
package now supports cleanup functions, called after a test or benchmark has finished, by calling T.Cleanup
or B.Cleanup
respectively. Example,
func TestFunction(t *testing.T) {
// setup code
// sub-tests
t.Run()
t.Run()
...
// cleanup
t.Cleanup(func(){
//tear-down code
})
}
Here, t.Cleanup runs after the test and all its sub-tests are complete.
Original answer (Feb. 2017)
As shown in the article "Go unit test setup and teardown" from Kare Nuorteva, you could use a setup function which returns... a teardown function to you defer.
See this gist:
func setupSubTest(t *testing.T) func(t *testing.T) {
t.Log("setup sub test")
return func(t *testing.T) {
t.Log("teardown sub test")
}
}
The setup function is in charge of defining and returning the teardown one.
For each test, for instance in a table-driven test scenario:
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)
}
})
}
答案2
得分: 2
如果表驱动的测试模式适合您,那么您应该坚持使用它。如果您需要更通用和灵活的解决方案,可以尝试使用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{})
}
英文:
If table driven test pattern works for you, you should stick with it. If you need something more generic and flexible feel free to give https://github.com/houqp/gtest a try.
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{})
}
答案3
得分: 0
你可以考虑创建一个函数表,包括subTestSetup、subTest和subTestTeardown,通过一个结构体传递数据库连接/其他共享项(subTestSetup可以返回该结构体)。你还可以在不同的函数中重复使用设置和拆卸的某些部分,以便在测试需求增加时保持模块化。在调用subTest之前,使用defer subTestTeardown(),以确保即使subTest出现问题,拆卸代码也会执行。
英文:
You may consider having a table of functions subTestSetup, subTest and subTestTeardown passing the db connection/other common items in a struct (subTestSetup can return this struct). You can possibly reuse some/parts of the setup & tear down in different functions too & keep this modular as your testing requirement grows. Call defer subTestTeardown() before you call the subTest, to ensure tear down code executes even if there's any issue with subTest.
答案4
得分: 0
⚠️ 注意:这不是一个经过完全测试的解决方案;它在一般的测试场景中似乎工作得很好。
在一个简单的情况下,我使用了自己实现的 t.Run()
函数,在子测试完成后进行清理:
func Run(t *testing.T, name string, f func(*testing.T)) bool {
result := t.Run(name, f)
// 进行清理操作。
return result
}
在我的测试中:
func TestSomethingWorks(t *testing.T) {
Run(t, "scenario 1", func(t *testing.T) {
// ...
})
Run(t, "scenario 2", func(t *testing.T) {
// ...
})
}
英文:
⚠️ NOTE: This is not a fully tested solution; it just seems to work very well in casual test scenarios.
In a simple case, I've used my own implementation of t.Run()
that does the cleaning after a sub-test has been finished:
func Run(t *testing.T, name string, f func(*testing.T)) bool {
result := t.Run(name, f)
// Do the cleanup.
return result
}
And in my tests:
func TestSomethingWorks(t *testing.T) {
Run(t, "scenario 1", func(t *testing.T) {
// ...
})
Run(t, "scenario 2", func(t *testing.T) {
// ...
})
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论