【问题标题】:How to unit test Thread Safe Generic List in C# using NUnit?如何使用 NUnit 在 C# 中对线程安全通用列表进行单元测试?
【发布时间】:2010-02-25 23:33:16
【问题描述】:

我问了一个关于构建自定义 Thread Safe Generic List 的问题,现在我正在尝试对其进行单元测试,但我完全不知道该怎么做。由于锁定发生在 ThreadSafeList 类中,我不确定如何在尝试模拟多个 add 调用时使列表锁定一段时间。谢谢。

Can_add_one_item_at_a_time

[Test]
public void Can_add_one_item_at_a_time() //this test won't pass
{
    //I am not sure how to do this test...
    var list = new ThreadSafeList<string>();

    //some how need to call lock and sleep inside list instance
    //say somehow list locks for 1 sec

    var ta = new Thread(x => list.Add("a"));
    ta.Start(); //does it need to aboard say before 1 sec if locked

    var tb = new Thread(x => list.Add("b"));
    tb.Start(); //does it need to aboard say before 1 sec if locked

    //it involves using GetSnapshot()
    //which is bad idea for unit testing I think
    var snapshot = list.GetSnapshot(); 
    Assert.IsFalse(snapshot.Contains("a"), "Should not contain a.");
    Assert.IsFalse(snapshot.Contains("b"), "Should not contain b.");
}

Snapshot_should_be_point_of_time_only

[Test]
public void Snapshot_should_be_point_of_time_only()
{
    var list = new ThreadSafeList<string>();
    var ta = new Thread(x => list.Add("a"));
    ta.Start();

    ta.Join();
    var snapshot = list.GetSnapshot();

    var tb = new Thread(x => list.Add("b"));
    tb.Start();

    var tc = new Thread(x => list.Add("c"));
    tc.Start();

    tb.Join();
    tc.Join();

    Assert.IsTrue(snapshot.Count == 1, "Snapshot should only contain 1 item.");
    Assert.IsFalse(snapshot.Contains("b"), "Should not contain a.");
    Assert.IsFalse(snapshot.Contains("c"), "Should not contain b.");
}

实例方法

public ThreadSafeList<T> Instance<T>()
{
    return new ThreadSafeList<T>();
}

【问题讨论】:

  • 由于 Thread.Sleep 导致这个测试非常慢,我建议使用thread.Join()。此外,此测试可能会失败,因为在您初始化 snapshot 时可能没有安排 ta
  • @Bas,谢谢,我已经更新了 Snapshot_should_be_point_of_time_only 测试。

标签: c# multithreading unit-testing thread-safety generic-list


【解决方案1】:

让我们看看你的第一个测试,Can_add_one_item_at_a_time

首先,您的退出条件没有意义。 应该添加两个项目,一次只一个。所以你的测试当然会失败。

您也不需要制作快照;请记住,这是一个测试,在您的测试运行时,不会有任何其他东西会触及列表。

最后但并非最不重要的一点是,您需要确保在所有线程真正完成之前,您不会尝试评估退出条件。最简单的方法是使用计数器和等待事件。这是一个例子:

[Test]
public void Can_add_from_multiple_threads()
{
    const int MaxWorkers = 10;

    var list = new ThreadSafeList<int>(MaxWorkers);
    int remainingWorkers = MaxWorkers;
    var workCompletedEvent = new ManualResetEvent(false);
    for (int i = 0; i < MaxWorkers; i++)
    {
        int workerNum = i;  // Make a copy of local variable for next thread
        ThreadPool.QueueUserWorkItem(s =>
        {
            list.Add(workerNum);
            if (Interlocked.Decrement(ref remainingWorkers) == 0)
                workCompletedEvent.Set();
        });
    }
    workCompletedEvent.WaitOne();
    workCompletedEvent.Close();
    for (int i = 0; i < MaxWorkers; i++)
    {
        Assert.IsTrue(list.Contains(i), "Element was not added");
    }
    Assert.AreEqual(MaxWorkers, list.Count,
        "List count does not match worker count.");
}

现在这确实有可能Add 发生得如此之快,以至于没有两个线程会同时尝试这样做。 不退款不退货部分解释了如何插入条件延迟。我实际上会定义一个特殊的测试标志,而不是DEBUG。在您的构建配置中,添加一个名为 TEST 的标志,然后将其添加到您的 ThreadSafeList 类中:

public class ThreadSafeList<T>
{
    // snip fields

    public void Add(T item)
    {
        lock (sync)
        {
            TestUtil.WaitStandardThreadDelay();
            innerList.Add(item);
        }
    }

    // snip other methods/properties
}

static class TestUtil
{
    [Conditional("TEST")]
    public static void WaitStandardThreadDelay()
    {
        Thread.Sleep(1000);
    }
}

只要构建配置定义了TEST 标志,这将导致Add 方法在实际添加项目之前等待1 秒。整个测试至少需要 10 秒;如果它完成得比这更快,那就有问题了。

考虑到这一点,我将把第二个测试留给你。很相似。

【讨论】:

    【解决方案2】:

    您需要插入一些 TESTONLY 代码,这会延迟您的锁定。你可以像这样创建一个函数:

     [Conditional("DEBUG")]
     void SleepForABit(int delay) { thread.current.sleep(delay); }
    

    然后在你的课堂上调用它。 Conditional 属性确保仅在 DEBUG 构建中调用它,您可以将其保留在已编译的代码中。

    写一些持续延迟 100 毫秒左右的东西和一些从不等待的东西,让他们把它解决掉。

    【讨论】:

      【解决方案3】:

      您可能想看看Chess。这是一个专门用于在多线程代码中查找竞争条件的程序。

      【讨论】:

        猜你喜欢
        • 2013-05-08
        • 1970-01-01
        • 1970-01-01
        • 2018-03-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-08-26
        相关资源
        最近更新 更多