August 21st, 2008 | Published in Google Testing
Often, threaded tests start out using sleeps to wait for something to happen. This test is trying to verify that DelegateToIntern spawns its work argument into a parallel thread and invokes a callback when it's done.
self.caffeinated = False
def DrinkCoffee(): self.caffeinated = True
self.assertFalse(self.caffeinated, "I watch YouTubework; intern brews")
time.sleep(60) # 1min should be long enough to make coffee, right?
self.assertTrue(self.caffeinated, "Where's mah coffee?!?")
Aside from abusing your intern every time you run the test, this test takes a minute longer than it needs to, and it may even fail when the machine (or intern!) is loaded in odd ways. You should always be skeptical of sleep statements, especially in tests. How can we make the test more reliable?
The answer is to explicitly control when things happen within DelegateToIntern with a threading.Event in Python, a Notification in C++, or a CountDownLatch(1) in Java.
is_started, can_finish, is_done = Event(), Event(), Event()
is_started.set() # Allow is_started.wait() to return.
# Wait up to 1min for can_finish.set() to be called. The timeout
# prevents failures from hanging, but doesn't delay a passing test.
can_finish.wait(timeout=60) # .await() in Java
self.assertTrue(is_started.isSet(), "FakeCoffeeMaker should have started")
self.assertFalse(is_done.isSet(), "Don't bug me before coffee's made")
can_finish.set() # Now let FakeCoffeeMaker return.
self.assertTrue(is_done.isSet(), "Intern should ping when coffee's ready")
Now we're guaranteed that no part of the test runs faster than we expect, and the test passes very quickly. It could run slowly when it fails, but you can easily lower the timeouts while you're debugging it.
We'll look at testing for race conditions in a future episode.
No interns were harmed in the making of this TotT.
Remember to download this episode of Testing on the Toilet and post it in your office.