假设我们有一个以下的方法:
在单元测试中,我们希望断言
在测试中,我们可以将
所以,当我们运行测试时,
这种解决方案唯一的问题是它覆盖了golang的基本结构,这不是正确的做法。
这些是我能找到的解决方案,但在我看来都不令人满意。哪个是最好的解决方案?
假设我们有以下这样的一个方法:
func method(intr MyInterface) {
go intr.exec()
}
在单元测试中,我们希望断言
method
已经被调用一次且仅一次;因此,在测试中,我们可以使用另一个模拟结构(mock struct)来模拟它,这将为我们提供检查它是否被调用的功能:type mockInterface struct{
CallCount int
}
func (m *mockInterface) exec() {
m.CallCount += 1
}
在单元测试中:
func TestMethod(t *testing.T) {
var mock mockInterface{}
method(mock)
if mock.CallCount != 1 {
t.Errorf("Expected exec to be called only once but it ran %d times", mock.CallCount)
}
}
现在的问题是,由于使用了go
关键字调用了intr.exec
,我们无法确定当我们在测试中达到断言时,它是否已被调用。
可能的解决方案1:
将一个通道添加到intr.exec
的参数中可以解决这个问题:我们可以在测试中等待接收任何来自通道的对象,在从通道接收到对象后继续断言其是否被调用。这个通道将在生产(非测试)代码中完全未被使用。这种方法可以解决问题,但会给非测试代码增加不必要的复杂性,并使大型代码库难以理解。
可能的解决方案2:
在测试中在断言之前添加相对较小的延迟可能会让我们有一些保证,在延迟结束之前goroutine将被调用:
func TestMethod(t *testing.T) {
var mock mockInterface{}
method(mock)
time.sleep(100 * time.Millisecond)
if mock.CallCount != 1 {
t.Errorf("Expected exec to be called only once but it ran %d times", mock.CallCount)
}
}
这将保留非测试代码的现状。
问题在于它会使测试变慢,并且会使它们变得不稳定,因为它们可能会在某些随机情况下失败。
可能的解决方案3:
创建一个像这样的实用程序函数:
var Go = func(function func()) {
go function()
}
将method
重写为如下形式:
func method(intr MyInterface) {
Go(intr.exec())
}
在测试中,我们可以将
Go
更改为以下内容:var Go = func(function func()) {
function()
}
所以,当我们运行测试时,
intr.exec
将被同步调用,我们可以确保在断言之前调用我们的模拟方法。这种解决方案唯一的问题是它覆盖了golang的基本结构,这不是正确的做法。
这些是我能找到的解决方案,但在我看来都不令人满意。哪个是最好的解决方案?
runtime.Gosched()
,它可以在没有任何睡眠延迟的情况下让出给另一个goroutine。 - AdrianGosched
不能让我选择哪个goroutine被选中,我是对的吧?我们正在并行地运行测试,我担心这会带来问题。 - sazary