如何告诉我的测试在goroutine中等待回调?

huangapple go评论81阅读模式
英文:

How do I tell my test to wait for a callback in a goroutine?

问题

我正在使用dockerclient https://github.com/samalba/dockerclient,它具有基于通道的API来监听事件client.MonitorEvents(),以及一个方便的回调方法client.StartMonitorEvents(callbackHandler)

我想测试处理程序是否被调用。当然,dockerclient会在goroutine中处理事件。

目前,我的处理程序只是输出一个日志。如果我在测试中等待,一切都会被处理。如果我不等待,它会在处理任何内容之前退出:

func eventCallback(event *dockerclient.Event, ec chan error, args ...interface{}) {
  log.Printf("Received event: %#v\n", *event)
}

我的测试似乎很简单:

func TestReceiveEvent(t *testing.T) {
   createAndMonitorEvents(server.URL)
   <- eventReady
   eventWriter.Write([]byte(someEvent))
   // 在这里进行一些测试
}

当然,除非我放入time.Sleep(),否则它不起作用,因为有goroutine。

除了放入一些任意的睡眠时间,我如何告诉我的测试程序“在运行测试之前等待另一个例程完成其工作”?我希望测试事件是否由我的处理程序正确处理。

另外一个接口client.MonitorEvents()返回一个通道,这样我可以更好地控制,但是从通道接收到的是无限的nil事件。

更新:

根据要求,createAndMonitorEvents如下:

func createAndMonitorEvents(url string) {
  // 初始化客户端
  docker, _ := dockerclient.NewDockerClient(url, nil)

  // 监听事件
  stopchan := make(chan struct{})

  go func() {
    eventErrChan, err := docker.MonitorEvents(nil, stopchan)
    if err != nil {
      return
    }

    for e := range eventErrChan {
      if e.Error != nil {
        return
      }
      eventCallback(&e.Event, nil)
    }
    fmt.Println("monitor in place")
  }()
}
英文:

I am using the dockerclient https://github.com/samalba/dockerclient which has a channel-based API to listen for events client.MonitorEvents() and a convenient callback method client.StartMonitorEvents(callbackHandler).

I want to test that the handler gets called. Of course, the dockerclient handles the events in a goroutine.

For now, my handler just spits out a log. If I wait in my test, everything is handled. If I do not, it exits before it handles anything:

func eventCallback(event *dockerclient.Event, ec chan error, args ...interface{}) {
  log.Printf(&quot;Received event: %#v\n&quot;, *event)
}

My test seems straightforward:

func TestReceiveEvent(t *testing.T) {
   createAndMonitorEvents(server.URL)
   &lt;- eventReady
   eventWriter.Write([]byte(someEvent))
   // test for something here
}

Of course, it doesn't work unless I put in a time.Sleep() because of the goroutine.

How do I tell my test, "wait for the other routine to do its work before running the test", other than putting in some arbitrary sleep? I am looking to test that the event is processed correctly by my handler.

The alternate interface, client.MonitorEvents() returns a channel, which gives me greater control, but the receive off the channel spits out infinite nil events.

UPDATE:

As requested, createAndMonitorEvents is:

func createAndMonitorEvents(url string) {
  // Init the client
  docker, _ := dockerclient.NewDockerClient(url, nil)

  // Listen to events
  stopchan := make(chan struct{})

  go func() {
	eventErrChan, err := docker.MonitorEvents(nil, stopchan)
	if err != nil {
		return
	}

	for e := range eventErrChan {
		if e.Error != nil {
			return
		}
		eventCallback(&amp;e.Event, nil)
	}
	fmt.Println(&quot;monitor in place&quot;)
  }()
}

答案1

得分: 1

我认为当你使用MonitorEvents时得到nil值时,只是表示事件通道已关闭(MonitorEvents的源代码中包含close(eventOrErrorChan),支持这一点)。evt, ok := <-c可以直接检查是否关闭(当关闭时,ok将为false),而for evt := range c会在通道关闭后停止。通常情况下,从关闭的通道接收数据会返回元素类型的零值,之前发送的值已经被接收。

关于等待回调的问题:回调函数可以关闭一个通道(或向其发送数据)。然后,你的测试可以使用select语句等待一段指定的时间:

select {
case <-c:
    /* ...成功处理... */
case <-time.After(5 * time.Second):
    /* 超时处理 */
}

如果你知道某些错误条件会导致处理程序无法完成或无法运行,可以通过在不同的通道上发送信号或向c发送不同的值来表示这些情况。

英文:

I think when you get nils with MonitorEvents, you're just seeing that the event channel is closed (the source of MonitorEvents includes a close(eventOrErrorChan), supporting this). evt, ok := &lt;-c lets you directly check if that (ok will be false when it's closed), and for evt := range c will stop after it's closed. In general, receiving from a closed channel is specified to "[yield] the element type's zero value after any previously sent values have been received"

On the question about waiting on a callback: the callback can close a channel. (Or send to it.) Then your test can wait up to a specified length of time with a select:

select {
case &lt;-c:
        /* ...success... */
case &lt;-time.After(5 * time.Second):
        /* timed out */
}

If you know some error conditions cause the handler not to finish, or not to run, it could signal those situations on a different channel, or by sending a different value to c.

答案2

得分: 1

我认为这可以帮助你。

WaitGroup用于等待一组goroutine完成。主goroutine调用Add来设置要等待的goroutine数量。然后每个goroutine运行并在完成时调用Done。同时,可以使用Wait来阻塞,直到所有goroutine都完成。

http://golang.org/pkg/sync/#example_WaitGroup

英文:

I think this can help

>A WaitGroup waits for a collection of goroutines to finish. The main goroutine calls Add to set the number of goroutines to wait for. Then each of the goroutines runs and calls Done when finished. At the same time, Wait can be used to block until all goroutines have finished.

http://golang.org/pkg/sync/#example_WaitGroup

答案3

得分: 0

如何告诉我的测试:“在运行测试之前,等待另一个例程完成其工作”,而不是使用任意的延时?你可以通过发送或关闭通道来实现。调用者在接收到信号之前会阻塞执行。不过,我真的不明白如何将你的代码整合成有意义的东西...你不能在函数中分配停止通道,你必须将其传递给函数,这样调用者就可以监听它。这段代码怎么能编译通过呢?

func TestReceiveEvent(t *testing.T) {
   createAndMonitorEvents(server.URL)
   <- eventReady // 未声明的变量,并且丢弃了你接收到的事件?
   eventWriter.Write([]byte(someEvent)) // 另一个未声明的变量
   // 在这里进行一些测试
}

也许以下的想法可以帮助你入门...

func createAndMonitorEvents(url string, done chan bool) {
      // 代码
      close(done)
}

func TestReceiveEvent(t *testing.T) {
  eventReady := make(chan bool)
    createAndMonitorEvents(server.URL, eventReady)
    <- eventReady
    eventWriter.Write([]byte(someEvent)) // 不过这个变量仍然不存在
    // 在这里进行一些测试
}
英文:

"How do I tell my test, "wait for the other routine to do its work before running the test", other than putting in some arbitrary sleep?"

You either send on a channel or close one. Where the caller is receiving execution blocks until a signal occurs. I don't really see how to work your code into something that makes sense though... You can't allocate the stop channel in the function, you have to pass it to the function so the caller can listen on it. Like how does this even compile?

func TestReceiveEvent(t *testing.T) {
   createAndMonitorEvents(server.URL)
   &lt;- eventReady // undeclared variable, and discarding the event you recieved?
   eventWriter.Write([]byte(someEvent)) //and another variable that is not declared
   // test for something here
}

Maybe and idea that will help get you started...

func createAndMonitorEvents(url string, done chan bool) {
      //the codes
      close(done)
}


func TestReceiveEvent(t *testing.T) {
  eventReady := make(chan bool)
    createAndMonitorEvents(server.URL, eventReady)
    &lt;- eventReady
    eventWriter.Write([]byte(someEvent)) // dis still don&#39;t exist though
    // test for something here

}

huangapple
  • 本文由 发表于 2015年7月31日 23:39:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/31750100.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定