如何验证在单独的 go 例程中调用了一个方法
How to validate that a method is called in a separate go routine
在为一个方法编写单元测试时,我遇到了一个问题。一、待测代码片段:
func MehodToBeTested(e Entity) {
go saveAudit(e)
//do something on which assertions can be done
}
实体可以被模拟。在 saveAudit 方法中,调用了 Entity.Save 方法。在我的 UT 中,我想断言 Entity.Save 方法被调用一次。以下是我目前的 UT:
func TestMethod(t *testing.T) {
var mock = &mockEntity{}
mock.On("Save").Return(nil)
//make call to func under test
MethodToBeTested(mock)
// Assert that Save is called on Entity
mock.AssertNumberOfCalls(t, "Save",1)
}
这是错误提示:预期调用次数 (1) 与实际调用次数 (0) 不匹配,因为实际调用发生在另一个 go 例程中。我该如何测试?
我使用相同的技术。等待 goroutine 结束。很可能还没有设置。
此外,我建议 运行 使用竞争条件检测器进行此类测试。它有助于捕捉这种情况。然后您可以向测试添加一些同步以使其可靠。
我的测试示例。被测试的函数应该并行检查两个网页是否包含指定的字符串。所以测试应该检查测试函数是否访问了两个页面
更新:附加了不正确的测试。固定的。
func TestCheckSites_TwoSlowHandlers_BothContain(t *testing.T) {
var config = GetConfig()
var v1, v2 bool
var wg sync.WaitGroup
wg.Add(2)
handler1 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer wg.Done()
v1 = true
time.Sleep(2 * config.Http.Timeout) // Use double HTTP_TIMEOUT
io.WriteString(w, "Present")
})
ts1 := httptest.NewServer(handler1)
defer ts1.Close()
handler2 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer wg.Done()
v2 = true
time.Sleep(2 * config.Http.Timeout)
io.WriteString(w, "Present")
})
ts2 := httptest.NewServer(handler2)
defer ts2.Close()
result, err := checkSites([]string{ts1.URL, ts2.URL}, "Present")
assert.Equal(t, nil, err, "Error should be nil")
assert.Contains(t, []string{""}, result, "Should be empty string")
//assert.(t, ts1.URL, result, "Should first or second empty string")
wg.Wait()
assert.Equal(t, true, v1, "First server should be visited")
assert.Equal(t, true, v2, "Second server should be visited")
}
首先,您所做的并不是我认为的真正的单元测试,因为您是同时测试多个单元。对于"true"单元测试,分别测试每个函数:
func TestMethodToBeTested(t *testing.T) {
// Test the main function
func TestAuditSave(t *testing.T) {
// Test the code executed in the goroutine
有了这种关注点分离,剩下要做的就是不(有意义地)在执行TestMethodToBeTested
时执行goroutine。这可以通过多种方式完成:
- 如果
saveAudit
的行为可以忽略,就忽略它——但也不要测试它。
可以将它移到接口或其他变量中,以便可以将存根放入它的位置。示例:
func (x *X) MethodToBeTested(e Entity) {
go x.saveAudit(e)
// more code
}
通过这种方式,您可以在测试中替换虚拟 saveAudit
方法。
后者是我通常推荐的方法,即使在使用非 go-routines 时也是如此,因为它可以很容易地单独测试每个组件(即我所说的 "true" 单元测试)。
@Flimzy 写在这里是因为评论不能包含很好的代码示例。希望没关系。考虑以下(愚蠢,但为了举例):
type MyStruct struct {
counter int
}
func (s *MyStruct) Add(item string) {
s.ConfirmAdd()
}
func (s *MyStruct) ConfirmAdd() {
s.counter++
}
ConfirmAdd()
的测试如下
func TestConfirmAdd(t *testing.T) {
s := MyStruct{}
s.ConfirmAdd()
Assert(s.counter, Equals, 1)
}
在为 Add()
编写测试时,您会什么都不写吗?不断言 ConfirmAdd()
被调用感觉很糟糕。
在为一个方法编写单元测试时,我遇到了一个问题。一、待测代码片段:
func MehodToBeTested(e Entity) {
go saveAudit(e)
//do something on which assertions can be done
}
实体可以被模拟。在 saveAudit 方法中,调用了 Entity.Save 方法。在我的 UT 中,我想断言 Entity.Save 方法被调用一次。以下是我目前的 UT:
func TestMethod(t *testing.T) {
var mock = &mockEntity{}
mock.On("Save").Return(nil)
//make call to func under test
MethodToBeTested(mock)
// Assert that Save is called on Entity
mock.AssertNumberOfCalls(t, "Save",1)
}
这是错误提示:预期调用次数 (1) 与实际调用次数 (0) 不匹配,因为实际调用发生在另一个 go 例程中。我该如何测试?
我使用相同的技术。等待 goroutine 结束。很可能还没有设置。
此外,我建议 运行 使用竞争条件检测器进行此类测试。它有助于捕捉这种情况。然后您可以向测试添加一些同步以使其可靠。
我的测试示例。被测试的函数应该并行检查两个网页是否包含指定的字符串。所以测试应该检查测试函数是否访问了两个页面
更新:附加了不正确的测试。固定的。
func TestCheckSites_TwoSlowHandlers_BothContain(t *testing.T) {
var config = GetConfig()
var v1, v2 bool
var wg sync.WaitGroup
wg.Add(2)
handler1 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer wg.Done()
v1 = true
time.Sleep(2 * config.Http.Timeout) // Use double HTTP_TIMEOUT
io.WriteString(w, "Present")
})
ts1 := httptest.NewServer(handler1)
defer ts1.Close()
handler2 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer wg.Done()
v2 = true
time.Sleep(2 * config.Http.Timeout)
io.WriteString(w, "Present")
})
ts2 := httptest.NewServer(handler2)
defer ts2.Close()
result, err := checkSites([]string{ts1.URL, ts2.URL}, "Present")
assert.Equal(t, nil, err, "Error should be nil")
assert.Contains(t, []string{""}, result, "Should be empty string")
//assert.(t, ts1.URL, result, "Should first or second empty string")
wg.Wait()
assert.Equal(t, true, v1, "First server should be visited")
assert.Equal(t, true, v2, "Second server should be visited")
}
首先,您所做的并不是我认为的真正的单元测试,因为您是同时测试多个单元。对于"true"单元测试,分别测试每个函数:
func TestMethodToBeTested(t *testing.T) {
// Test the main function
func TestAuditSave(t *testing.T) {
// Test the code executed in the goroutine
有了这种关注点分离,剩下要做的就是不(有意义地)在执行TestMethodToBeTested
时执行goroutine。这可以通过多种方式完成:
- 如果
saveAudit
的行为可以忽略,就忽略它——但也不要测试它。 可以将它移到接口或其他变量中,以便可以将存根放入它的位置。示例:
func (x *X) MethodToBeTested(e Entity) { go x.saveAudit(e) // more code }
通过这种方式,您可以在测试中替换虚拟
saveAudit
方法。
后者是我通常推荐的方法,即使在使用非 go-routines 时也是如此,因为它可以很容易地单独测试每个组件(即我所说的 "true" 单元测试)。
@Flimzy 写在这里是因为评论不能包含很好的代码示例。希望没关系。考虑以下(愚蠢,但为了举例):
type MyStruct struct {
counter int
}
func (s *MyStruct) Add(item string) {
s.ConfirmAdd()
}
func (s *MyStruct) ConfirmAdd() {
s.counter++
}
ConfirmAdd()
的测试如下
func TestConfirmAdd(t *testing.T) {
s := MyStruct{}
s.ConfirmAdd()
Assert(s.counter, Equals, 1)
}
在为 Add()
编写测试时,您会什么都不写吗?不断言 ConfirmAdd()
被调用感觉很糟糕。