当前位置: 首页 > 知识库问答 >
问题:

在Golang进行单元测试时,如何测试是否调用了goroutine?

施飞雨
2023-03-14

假设我们有这样一种方法:

func method(intr MyInterface) {
    go intr.exec()
} 

在单元测试方法中,我们希望断言相互作用。exec被调用过一次,而且只有一次;因此,我们可以在测试中使用另一个模拟结构来模拟它,这将为我们提供检查它是否已被调用的功能:

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

现在,问题是,由于intr.exec是用go关键字调用的,我们不能确定当我们在测试中到达断言时,它是否被调用过。

intr的参数添加通道。exec可以解决这个问题:我们可以在测试中等待从它接收任何对象,然后在从它接收对象之后,我们可以继续断言它正在被调用。在生产(非测试)代码中,此通道将完全未使用。这会起作用,但会给非测试代码增加不必要的复杂性,并可能使大型代码库变得不可理解。

在断言之前向测试中添加一个相对较小的睡眠可能会给我们一些保证,在睡眠完成之前会调用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)
    }
}


问题是它会使测试变慢,并且会使测试变得不稳定,因为它们可能会在一些随机的情况下中断。

创建这样的实用程序函数:

var Go = func(function func()) {
    go function()
} 

然后像这样重写方法

func method(intr MyInterface) {
    Go(intr.exec())
} 

在测试中,我们可以将Go更改为:

var Go = func(function func()) {
    function()
} 

所以,当我们运行测试时,intr。exec将被同步调用,我们可以确保在断言之前调用我们的mock方法<这个解决方案的唯一问题是,它凌驾于golang的基本结构之上,这是不正确的。

这些都是我能找到的解决方案,但就我所见,这些都是令人满意的。什么是最好的解决方案?


共有3个答案

易京
2023-03-14

首先,我会使用模拟生成器,即github.com/gojuno/minimock而不是自己写模拟:

例如minimock-f。go-i MyInterface-o my_interface_mock_测试。去

然后你的测试可以看起来像这样(顺便说一句,测试存根也是用github.com/hexdigest/gounit生成的)

func Test_method(t *testing.T) {
    type args struct {
        intr MyInterface
    }
    tests := []struct {
        name string
        args func(t minimock.Tester) args
    }{
        {
            name: "check if exec is called",
            args: func(t minimock.Tester) args {
                return args{
                    intr: NewMyInterfaceMock(t).execMock.Return(),
                }
            },
        },
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            mc := minimock.NewController(t)
            defer mc.Wait(time.Second)

            tArgs := tt.args(mc)

            method(tArgs.intr)
        })
    }
}

在这次测试中

defer mc.Wait(time.Second)

等待调用所有模拟的方法。

左丘照
2023-03-14

这个测试不会像同步一样永远挂起。上面提出的WaitGroup解决方案。它将挂起一秒钟(在这个特定的例子中),以防没有调用mock.exec:

package main

import (
    "testing"
    "time"
)

type mockInterface struct {
    closeCh chan struct{}
}

func (m *mockInterface) exec() {
    close(closeCh)
}

func TestMethod(t *testing.T) {
    mock := mockInterface{
        closeCh: make(chan struct{}),
    }

    method(mock)

    select {
    case <-closeCh:
    case <-time.After(time.Second):
        t.Fatalf("expected call to mock.exec method")
    }
}

这基本上就是mc。在我上面的回答中等待(时间。秒)。

龚振濂
2023-03-14

在mock中使用sync. WaitGroup

您可以扩展mockInterface以允许它等待其他goroutine完成

type mockInterface struct{
    wg sync.WaitGroup // create a wait group, this will allow you to block later
    CallCount int
}

func (m *mockInterface) exec() {
    m.wg.Done() // record the fact that you've got a call to exec
    m.CallCount += 1
}

func (m *mockInterface) currentCount() int {
    m.wg.Wait() // wait for all the call to happen. This will block until wg.Done() is called.
    return m.CallCount
}

在测试中,您可以执行以下操作:

mock := &mockInterface{}
mock.wg.Add(1) // set up the fact that you want it to block until Done is called once.

method(mock)

if mock.currentCount() != 1 {  // this line with block
    // trimmed
}
 类似资料:
  • 问题内容: 如果是跑它运行在您的文件结尾通过运行启动格式的功能和使用(* T testing.T)模块。我想知道文件中的每个功能是同时运行还是确定地分别运行每个功能?是否为每个人创建一个执行例程?如果确实为每个例程创建了一个go例程,是否可以某种方式监视go例程?是否有可能做类似的事情并为每个实例获取一个实例并对其进行监控,诸如此类? 注意:这个问题假设您使用go(测试)随附的测试框架。 问题答案

  • 单元测试,对独立的代码功能片段,由编写代码的团队进行测试,也是一种编码,而非与之不同的一些事情。设计代码的一部分就是设计它该如何被测试。你应该写一个测试计划,即使它只是一句话。有时候测试很简单:“这个按钮看起来好吗?”,有时候它很复杂:“这个匹配算法可以精确地返回正确的匹配结果?”。 无论任何可能的时候,使用断言检查以及测试驱动。这不仅能尽早发现 bug,而且在之后也很有用,让你在其他方面担心的谜

  • 问题内容: 如何在单元测试中测试 hashCode()函数? 问题答案: 每当我覆盖equals和hash代码时,我都会按照Joshua Bloch在“ Effective Java”第3章中的建议编写单元测试。我确保equals和hash代码是自反的,对称的和可传递的。我还确保“不等于”对所有数据成员均正常工作。 当我检查对equals的调用时,我还要确保hashCode的行为符合预期。像这样:

  • 问题内容: 我一直在学习AngularJS,并且在单元测试方面进展非常顺利,但是我遇到了一个棘手的问题。 假设我有一个简单的表格,例如: 如果我正在测试类似控制器的东西,我知道我会这样写(使用Jasmine + Karma): 但是我不知道我需要注入哪些服务,也没有运气在指南或文档中找到有关单元测试的文档。 一个单元如何在Angular中测试表单? 问题答案: 我不认为这是对此类内容进行单元测试的

  • 问题内容: 我正在使用带有eclipse的junit编写功能测试。 当运行单个测试时,它将按照我在类中设置它们的顺序运行。 例如。 但是,当我将此测试作为套件的一部分运行时(在包中),顺序是随机的。 例如,它将执行验证,然后删除用户,然后删除joinuserToRoom然后创建用户。 我在套件中的测试并不相互依赖。但是,测试中的每个单独测试都取决于它们以正确的顺序运行。 有什么办法可以实现? 谢谢

  • 问题内容: 我选择的数据库是MongoDB。我正在编写一个数据层API,以从客户端应用程序中抽象实现细节- 也就是说,我实质上是在提供一个公共接口(一个充当IDL的对象)。 我正在以TDD方式测试自己的逻辑。在每个单元测试之前,调用一个方法来创建数据库单例,此后,当测试完成时,将调用一个方法来删除数据库。这有助于促进单元测试之间的独立性。 几乎所有单元测试(即 执行上下文查询 )都需要先进行某种插