Tag-Archive for ◊ parallel code ◊

Author:
Thursday, March 15th, 2012

Unit testing code that is spawned as a separate thread can be challenging.

We need to make sure our Asserts can run after the parallel thread completes, and not before.

Moreover, we need to make sure our unit test doesn’t complete before the parallel thread, leaving it orphan and causing errors for the test agent.

Consider the following code:

        private void LoadCustomerData()
        {
            this.IsLoading = true;

            // the following code will run a separate thread (use a BackgroundWorker if you like)
            Task.Factory.StartNew(() =>
                                      {
                                          // do some stuff here to load the data
                                          this.Customers = dao.GetCustomers();

                                          // finally indicate that loading is completed
                                          this.IsLoading = false;
                                      });
        }

Now consider a unit for this:

        [TestMethod]
        public void LoadData_should_bla_bla()
        {
            // Arrange
            var testObject = new SomeClass();

            // Act
            testObject.LoadData();

            // Assert
            Assert.IsNotNull(testObject.Customers);
        }

The test looks pretty straight forward. However, when you run this, it’ll not pass. You’ll most probably get the “agent terminated before execution completed” error or your assert will fail.

The reason is this: this.Customers is set by a separate thread, and as soon as you call LoadCustomerData(), it spawns that thread and returns immediately. Giving no time for the actual load to happen before you can do your Assert. In other words, your assert runs before the thread that loads the data.

OK, how to we solve this? Well, my first attempt would be to wait before I assert:

        [TestMethod]
        public void LoadData_should_bla_bla()
        {
            // Arrange
            var testObject = new SomeClass();

            // Act
            testObject.LoadData();

            while (testObject.IsLoading)
                ; // do nothing till loading completes

            // Assert
            Assert.IsNotNull(testObject.Customers);
        }

Again, at first look this looks pretty straight forwards. Your test would also work pass now. However, you’ll hit trouble when the test fails. What if something goes wrong when loading and IsLoading is never set to false. Well, your test will run indefinitely!

Ideally, your test should fail if for some reason IsLoading is not set to false. So, we could try a “wait with timeout” approach. Instead of waiting indefinitely, we can wait for some time (say 2 seconds) and throw a timeout exception after that (which will make the test fail). May be something like this:

        [TestMethod]
        public void LoadData_should_bla_bla()
        {
            // Arrange
            var testObject = new SomeClass();

            // Act
            testObject.LoadData();

            // Wait until IsLoading becomes false or for 2 seconds, whichever occurs first
            TestUtility.WaitWithTimeout(() => testObject.IsLoading == false, 2000);

            // Assert
            Assert.IsNotNull(testObject.Customers);
        }

There you go. This would work!

And here’s the code for this WaitWithTimeout function:

    class TestUtility
    {
        internal static void WaitUntil(Func<bool> whatToWaitFor, int howManyMilliseconds)
        {
            DateTime start = DateTime.Now;

            while (!whatToWaitFor())
            {
                Thread.Sleep(100); // just breathe a little instead of a continuous loop!

                if (DateTime.Now.Subtract(start).TotalMilliseconds > howManyMilliseconds)
                {
                    throw new TimeoutException("Wait timed out");
                }
            }
        }

Cheers!

-Anand

        private void LoadInitialData()
        {
            Loading = true;
            Task.Factory.StartNew(() =>
                                      {
                                          if (BranchTeams == null) // we only need to load first time
                                          {
                                              LoadBranchTeamData();
                                              if (App.StartupSurveyId > 0)
                                              {
                                                  SurveyFilter = App.StartupSurveyId;
                                              }

                                          }

                                          ApplyFilters();

                                          Loading = false;
                                      });
        }
        [TestMethod]
        public void LoadData_should_bla_bla()
        {
            // Arrange
            var testObject = new SomeClass();

            // Act
            testObject.LoadData();

            // Assert
            Assert.IsNotNull(testObject.Customers);
        }