Idioms, suggestions, gotchas for testing channels?

2,388 views
Skip to first unread message

Tim Ford

unread,
Dec 23, 2011, 3:35:05 AM12/23/11
to golang-nuts
I have a function that, when run as a goroutine, receives messages,
process them, and possibly sends messages back.

I want to unit-test this function. I want to test that, given certain
inputs, I receive a correct message back. I also want to test that,
given different inputs, I don't receive any messages.

Here's a bare-bones example showing what I'm trying to do:
http://play.golang.org/p/KYBNXtlDrM

So, as you can see, that code won't work because it can't catch the
error case of the function not sending a message when it should. So
how do I test that function?

I can think of some options.

1: Use a timeout. If it times out before we received a message, then
we assume it never sent a message. This seems problematic to me,
because how do we know the goroutine wasn't just taking its time? How
do I know how long to make the timeout for? Too long and my unit
tests take a long time. Too short

2: Don't use channels from the function. Return value,ok, and make ok
false when a message should not be sent. Wrap that function in an
outer function, which does the channel operations. Only unit-test the
inner function. This would work in this particular simplistic case,
but not in all cases. One of the nice things about channels is that
they allow us to send messages without having to return from a
function. An illustration of this is the lexical scanner designed by
Rob Pike: http://cuddle.googlecode.com/hg/talk/lex.html#slide-18

So I don't like 2.

3: Always return a value on the channel, but sometimes it's invalid.
I don't like this because it assumes I can come up with an invalid
value. What if all possible values of the message are legitimate?
Introduce a new field to the message struct, called "ok", that is
false don't want to send a message? Seems wasteful.

4: Introduce a new channel that exists only to signal to a testing
function that we're not going to be sending a message. The testing
function multiplexes on bChan and noopChan. This would work, but it
seems ugly to add a new channel only for testing. I also am not sure
it would work for all algorithms, though I'm having trouble coming up
with a specific example for which it wouldn't work.

So, has anyone else run into this? Any suggestions?

David Symonds

unread,
Dec 23, 2011, 6:29:38 PM12/23/11
to Tim Ford, golang-nuts
On Fri, Dec 23, 2011 at 7:35 PM, Tim Ford <tim.f...@gmail.com> wrote:

> 2: Don't use channels from the function.  Return value,ok, and make ok
> false when a message should not be sent.  Wrap that function in an
> outer function, which does the channel operations.  Only unit-test the
> inner function.  This would work in this particular simplistic case,
> but not in all cases.  One of the nice things about channels is that
> they allow us to send messages without having to return from a
> function.  An illustration of this is the lexical scanner designed by
> Rob Pike: http://cuddle.googlecode.com/hg/talk/lex.html#slide-18
>
> So I don't like 2.

Unless the channels are a fundamental part of what your function is
doing, this is, in fact, the more common way to approach this: write a
function to do the work. It's easy to write the wrapping function if
you need it to run asynchronously. Channels are great, but they can
also be used gratuitously, and perhaps that's what you're doing here.


Dave.

Dmitry Vyukov

unread,
Dec 25, 2011, 6:42:01 AM12/25/11
to Tim Ford, golang-nuts
Looks like the most reasonable option to me. Always return a pair (value, error), that's how API's in Go generally work. If you send nothing on an invalid message a client will potentially silently block on the channel forever which is not a good thing.
Depending on your context you may consider just panicing and terminating the program on invalid message.
I am not saying that it's a good solution, but you manage to close the channel then any attempt to send a message on the channel will automatically panic. Potentially it can make sense in some unit tests. However once again you will need to wait some time before returning from main, just to give the goroutine a change to send on the channel.

wkharold

unread,
Dec 26, 2011, 6:21:39 PM12/26/11
to golang-nuts
You could also abstract the channels into an interface a'la:
http://play.golang.org/p/fGD6AMt9ki

Then you could use implementations of the Pipe interface that have the
appropriate (mis)behavior in your tests.

... WkH

Kyle Lemons

unread,
Dec 27, 2011, 4:39:02 PM12/27/11
to Tim Ford, golang-nuts
In addition to the other suggestions, one possibility is to fire up a goroutine for each test case, send the input messages, close the input channel, and then (after the function exits) compare the outputs.  That way, you know the function isn't waiting to send anything else.

var messageTests = []struct{
   Input []Message
   Output []Message
}{
  {...},
  {...},
}

func TestMessage(t *testing.T) {
  for testId, test := range messageTests {
    got := []Message{}
    in, out := make(chan Message), make(chan Message)
    done := make(chan bool)
    go func() {
      for _, msg := range test.Input { in <- msg }
      close(in)
      done <- true
    }
    go func() {
      for msg := range out { got = append(got, msg) }
      done <- true
    }

    process := make(chan bool)
    go func() {
      messageProcessor(in, out)
      process <- true
    }
    select {
    case <-process:
    case <-time.After(ProcessTimeout):
      t.Fatalf("%d. messageProcessor timed out", testId)
    }

    // You can do timeouts on these too
    <-done
    <-done

    // compare got to test.Output
  }
}
Reply all
Reply to author
Forward
0 new messages