[英]How to unit test Thread Safe Generic List in C# using NUnit?
我问了一个关于构建自定义线程安全通用列表的问题,我现在正试图对它进行单元测试,我完全不知道该怎么做。 由于锁定发生在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>();
}
让我们来看看你的第一个测试, 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
发生的可能性,以至于没有两个线程会同时尝试执行它。 No Refunds No Returns部分解释了如何插入条件延迟。 我实际上会定义一个特殊的测试标志,而不是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秒钟; 如果它完成得比这更快,那就错了。
考虑到这一点,我将把第二次测试留给你。 它很相似。
您需要插入一些TESTONLY代码,以增加锁定的延迟。 你可以创建一个这样的函数:
[Conditional("DEBUG")]
void SleepForABit(int delay) { thread.current.sleep(delay); }
然后在你的课堂上调用它。 Conditional属性确保它仅在DEBUG构建中调用,您可以将其保留在已编译的代码中。
写一些一直延迟100M左右的东西和一些永不等待的东西,让它们把它搞得一团糟。
你可能想看看国际象棋 。 这是一个专门用于在多线程代码中查找竞争条件的程序。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.