test driven asynch tasks

2019-07-18 11:23发布

问题:

I am looking for general thoughts and/or links on the topic in general, although my specific motivation at the moment are UI tasks related to progress reporting using either BackgroundWorker and / or TPL. My experience level with async programming in general is novice. The testing tools I know best are NUnit and Rhino.

Some brain storming ideas off the top of my head:

  1. Don't bother - it's too complicated and you just wind up testing the BGW or TPL.
  2. Make some sort of fake or mock.
  3. Use EventWaitHandles

回答1:

Unit testing asynchronous code is not the simplest thing in the world, as I learned when writing unit tests for my Nito.Async library. :)

First, you want to define what you actually want to test. Do you just want to test whether the asynchronous action is performed, or do you want to ensure that your BGW/tasks are properly synchronizing their UI progress reporting?

Testing the action is pretty straightforward: just wait until the action is complete and then check for postconditions. (However, be aware that the BGW RunWorkerCompleted will be raised on a ThreadPool thread unless you give it a synchronization context like the example below).

Testing proper synchronization (e.g., that each piece of code is running on the correct thread) is more complex.

For each test you'll need to establish a synchronization context. This will be mocking the UI synchronization context. My Nito.Async library may help with that; it has an ActionThread which is a separate thread that contains a synchronization context suitable for owning EAP components (e.g., BGW) and scheduling tasks (e.g., TaskScheduler.FromCurrentSynchronizationContext).

It can be used like this (using an MSTest example):

[TestMethod]
public void Test()
{
  using (ActionThread thread = new ActionThread())
  {
    thread.Start();

    // Anything passed to Do is executed in that ActionThread.
    thread.Do(() =>
    {
      // Create BGW or call TaskScheduler.FromCurrentSynchronizationContext.
    });

    // The thread is gracefully exited at the end of the using block.
  }
}

I find Thread.CurrentThread.ManagedThreadId and Thread.CurrentThread.IsThreadPoolThread to be the easiest ways to check for proper synchronization. If your test code is run from within ActionThread.Do, then it should synchronize its progress updates (and completion notification) to that ActionThread.

A lot of the Nito.Async unit tests use ActionThread in this manner, so you could look there for various examples.