简体   繁体   中英

Two methods which can't run concurrently, how to write unit tests for this case, or make it testable?

Let's say I have two methods in my class MethodA and MethodB . Those methods are run in async, and I don't want that they run at the same time. In the app, MethodA is called by the user, but MethodB is run by a Timer in a background thread.

How I see the implementation in C#ish pseudocode:

class MyClass 
{
     private static object _lock = new object();

     public async Task MethodA()
     {
          lock(_lock)
          {
              await DoSomeStuff();
          }
     }

     public async Task MethodB()
     {
          if(Monitor.IsEntered(_lock)
                return;
          lock(_lock)
          {
              await DoSomeStuff();
          }
     }
}

So first question is - is above approach correct? I guess that's more a question for https://codereview.stackexchange.com/ .

So the second questions is - assuming that the approach is correct, how I can unit test, that MethodA waits for MethodB , and that MethodB doesn't run when MethodA is running? Or how can I refactor it so that's testable?

EDIT: accordingly to comments, changed from using flags to a lock.

Boolean flags are the obsolete way to synchronize two threads. It causes race conditions when one thread can read a value of false while other thread is updating the value to true ;

Since your case it not straightforward (B shouldn't way for B to end, while A should wait), then I would change the class use a Semaphore like this:

public class MyClass
{
    private SemaphoreSlim semaphore = new SemaphoreSlim(1);

    public async Task MethodA()
    {
        await semaphore.WaitAsync();

        await DoSomeStuff();

        semaphore.Release();
    }

    public async Task MethodB()
    {
        bool success = await semaphore.WaitAsync(1);

        if (!success)
            return;

        await DoSomeStuff();

        await Task.Delay(TimeSpan.FromSeconds(5));
        semaphore.Release();
    }
}

I would consider putting all that in try..catch..finally block and release the semaphore in the finally block, but i'm trying to keep it simple while you can add that yourself.

Unit testing:

This is not straight forward to test. Taking threads into account, you might need to repeat the test multiple times to reach a case of failure. You might need to introduce an overload for method A that waits for some times, which might prove that method B is waiting for it. Here is the test. To test the case of failure, change new SemaphoreSlim(1); to new SemaphoreSlim(2); and the test would fail because MethodB would start before MethodA ends.

[TestMethod]
public async Task TestingMyClassThreadSync()
{
    int repeatTimes = 100;

    int counter = 0;
    while (counter++ <= repeatTimes)
    {
        MyClass myClass = new MyClass();

        Task tA = myClass.MethodA();

        Task tB = myClass.MethodB();

        Task finishedTask = await Task.WhenAny(tA, tB);

        bool bFinishedBeforeA = finishedTask == tA;

        if (bFinishedBeforeA)
            Assert.Fail();
    }

}

I would introduce an overload:

public async Task MethodA(long waitMilliseconds = 0)
{
    await semaphore.WaitAsync();

    await DoSomeStuff();
    await Task.Delay(waitMilliseconds);
    semaphore.Release();
}

Then Call it from unit testing as MethodA(5000);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM