如何确保线程代码的覆盖率

How can I ensure coverage of threaded code

我正在尝试编写一个单元测试,它可以在 100% 的时间内命中一段线程代码以实现代码覆盖。代码只能在线程应用程序的上下文中访问,并且设计为每个对象实例只被命中一次,以最大限度地减少锁定时间开销。

举个简单的例子:

public class Example
{
  private readonly object lockObject = new object();
  private int? objectToInit = null;

  public void EnsureObjectInit()
  {
    //To minimize hitting the lock code, the object is checked first.
    if (!objectToInit.HasValue)
    {
      lock (lockObject)
      {
        //Then it is checked once more to ensure that it wasn't initiazlized
        //before the lock was hit.
        if (objectToInit.HasValue)
        {
          //This block can only be executed if a second thread is able to
          //get to the lock before the first can initialize the object.

          return; 
        }

        objectToInit = 0;
      }
    }
  }
}

为了获得代码覆盖率以命中第二个 if 语句中的代码,我试过这样的代码:

[TestClass]
public class ExampleTest
{
  [Test Method]
  public void async MyTest()
  {
    Example example = new Example();

    void runTask()
    {
      example.EnsureObjectInit();
    }

    //I've tried increasing the number of threads, but it doesn't make
    //it hit the noted code any more consistently.
    List<Task> tasks = new List<Task>(2);
    tasks.Add(Task.Run(runTask));
    tasks.Add(Task.Run(runTask));

    await Task.WhenAll(tasks);
    ... perform extra validation ...
  }
}

但是如果至少有 50% 的时间阻塞,则代码覆盖率无法到达内部。

有没有办法强制单元测试代码在初始化对象之前停止第一个线程,以便第二个线程可以进入第一个 "if" 块?

这有点乱七八糟,但您可以使用反射从示例 class 实例中获取 lockObject。然后在 ExampleTest 方法中手动锁定它。注意:这是一个高度耦合的测试。如果更改锁的名称,测试将失败。

    private class LockExample
    {
        private readonly object lockObject = new object();

        public void TestLock()
        {
            lock(lockObject)
            {
                //Do something
            }
        }
    }

    private class LockTest
    {
        public void Test()
        {
            var example = new LockExample();
            var lockOjbect = typeof(LockExample).GetField("lockObject", BindingFlags.NonPublic|BindingFlags.Instance).GetValue(example);
            lock (lockOjbect)
            {
                var task = Task.Run((Action)example.TestLock);
                task.Wait(1);   //allow the other thread to run
            }
        }
    }

稍作设计更改,您就可以使代码更易于测试:将对 objectToInit.HasValue 的访问权限提取到像 hasObjectToInitValue() 这样的辅助方法中。在您的测试中,您可以覆盖该方法,然后可以测试辅助方法在第一次调用 returns false 和第二次调用 returns true.[=14 时的场景=]