Posted by: felipe | March 21, 2010

Writing async tests

I don’t know about you, but I’m always amazed every time I see some clever usage of generator functions (the yield keyword). From things like speeding up your UI, to implementing thread pools, or computing prime numbers and infinite sequences, they’re always interesting. Last week, Shawn showed me a cool async pattern using generators which I really liked so I decided to blog about it.

Let’s start with the problem: I was writing some set of tests which were highly dependent on triggering actions and waiting events propagation to continue. This usually involves a lot of callbacks from short timeouts or event listeners, and you end up with code like this: [semi-pseudocode]

function goOn() {
  doThing3();
  setTimeout(finishUp, 10);
}

function test() {
  doThing1();
  setTimeout(function() {
    addEventListener("interesting-event", goOn, false);
    doThing2();
  }, 10);
}

Which is the modern day equivalent of spreading gotos everywhere on your code (“oh, the horror!”), and makes it very hard to follow the intended logic, specially if you didn’t aptly name your functions with numbers to help you out.

To improve that, we can avoid nesting all of the calls and write a sequential function, and use the yield keyword to exit the function at one point and then continue running it from that point on. It’s like a set of sleep/wakeUp calls, and all the wakeUp control won’t be so mixed with our logic. So we would write as follows:

function wakeUp() {
  testGen.next();
}
function test() {
   doThing1();
   setTimeout(wakeUp, 10); yield;
   doThing2();
   addEventListener("interesting-event", wakeUp, false); yield;
   doThing3();
   setTimeout(wakeUp, 10); yield;
   finishUp();   
}

Cleaner, isn’t it? Oh, and just a note, when you use this make sure you don’t end up subtly changing the logic of your calls, unlike “a friend of mine” who did this and broke the tests he had written. :)

About these ads

Responses

  1. Neat trick. Using generators rather than closures makes waiting-logic much easier to follow.

    It’s also important to use events rather than timeouts. My experience is that almost every test that uses a timeout to “wait for something” is intermittently orange. http://joblivious.wordpress.com/2009/02/20/handling-intermittence-how-to-survive-test-driven-development/ explains this issue well.

    If you specifically want to return to the event loop but not wait any longer, a 0ms setTimeout can work. But browsers tend to clamp setTimeout, so you need something like http://dbaron.org/log/20100309-faster-timeouts to be sure.

    • The trick of the zero timeout using postMessage is really cool. One thing I was wondering: if we exit the function with the specific goal of returning to the event loop, is it guaranteed that all the current pending events will be processed before the function being called again? Or does the 10~20ms clamping ended up helping us here?
      If it is guaranteed, I think it’d be really useful to have that ZeroTimeout function available in some helper file of the test harness. (EventUtils?)

      • I think that’s how it works — the events are treated as a queue, and you’re just posting one to the end of the queue.

      • SimpleTest.executeSoon is that helper function.

      • Great to know! then SimpleTest.executeSoon(wakeUp) it is

  2. Just make sure you don’t return early. ;)

  3. Oh, and it’s really good to see old CS concepts come back into fashion. I think coroutines are a very clean way to implement asynchronous logic, and that one of the worst effects of C being dominant for so many years was that coroutines fell into disuse.

    Interestingly, Simon Tatham (of PuTTY fame) has a hacked-up implementation of coroutines in C: http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html

    • That was a very cool read, thanks Sid.
      Another cool project related to this is node.js (nodejs.org). Have you seen it? It’s a JS library that makes all File I/O through events, even calls which are blocking in the underlying filesystem.

  4. [...] Had some fun with using yield for async tests [...]


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Categories

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: