竞争条件的单元测试
Unit test for race condition
考虑代码:
class TestClass
{
private bool _someFlag;
private object _sharedObject = new object();
private readonly object _syncObject = new object();
public object Read()
{
//lock (_syncObject)
{
_someFlag = false;
return _sharedObject;
}
}
public void Write(object obj)
{
//lock (_syncObject)
{
_someFlag = true;
_sharedObject = obj;
}
}
}
它有竞争条件问题。当我们调用 Read()
时,某些线程可以在 _someFlag = false;
和 return _sharedObject;
行之间调用 Write()
。我将通过 lock
操作员解决问题。但是你能帮我对这个竞争条件问题进行单元测试吗?
我不想出于测试目的或类似目的将 _someFlag
更改为 public
。
我想做这样的事情:
[Fact]
public void RaceConditionTest()
{
var correctObject = new object();
var test = new TestClass();
for (int i = 0; i < 1000; i++)
{
test.Write(correctObject);
var assertTask = Task.Run(() =>
{
var actualObj = test.Read();
Assert.True(object.ReferenceEquals(correctObject, actualObj), $"Failed on {i} iteration");
});
//Thread.Sleep(50);
var failTask = Task.Run(() => test.Write(new object()));
Task.WaitAll(assertTask, failTask);
}
}
但是我如何确定 assertTask
会在 failTask
之前启动?或者也许还有另一种方法可以对这种情况进行单元测试?提前致谢。
您可以在启动 failTask 之前检查 assertTask 是否 运行 或已完成:
while (assertTask.Status != Running && assertTask.Status != RanToCompletion)
Thread.Sleep(50);
我一直坚持这种方法。但仍在寻找更好的方法......
此测试在某些迭代中失败,但如果您取消注释 lock
运算符,测试将通过。
class TestClass
{
private IEventRecorder _eventRecorder;
private bool _someFlag;
private object _sharedObject = new object();
private readonly object _syncObject = new object();
#if DEBUG
public void SetEventRecorder(IEventRecorder eventRecorder) => _eventRecorder = eventRecorder;
#endif
public object Read()
{
//lock (_syncObject)
{
#if DEBUG
_eventRecorder?.Record(nameof(Read));
#endif
_someFlag = false;
return _sharedObject;
}
}
public void Write(object obj)
{
//lock (_syncObject)
{
#if DEBUG
_eventRecorder?.Record(nameof(Write));
#endif
_someFlag = true;
_sharedObject = obj;
}
}
public interface IEventRecorder
{
void Record(string eventName);
}
}
public class TestClassTests
{
private class EventRecorder : TestClass.IEventRecorder
{
private string _events = string.Empty;
public void Record(string eventName) => _events += eventName;
public string Events => _events;
public void Reset() => _events = string.Empty;
}
[Fact]
public void RaceConditionTest()
{
var correctObject = new object();
var eventRecorder = new EventRecorder();
var test = new TestClass();
test.SetEventRecorder(eventRecorder);
for (int i = 0; i < 1000; i++)
{
test.Write(correctObject);
var assertTask = Task.Run(() =>
{
var actualObj = test.Read();
if (eventRecorder.Events.StartsWith("WriteRead"))
Assert.True(object.ReferenceEquals(correctObject, actualObj), $"Failed on {i} iteration");
});
var failTask = Task.Run(() => test.Write(new object()));
Task.WaitAll(assertTask, failTask);
eventRecorder.Reset();
}
}
}
要测试并发问题,最好让所有线程在定义的时间点等待,然后全部释放。
var threadCount = someList.Length;
var threadsReady = new ManualResetEvent(false);
var threadsReadyCount = 0;
Task.WhenAll(someList.Select(item => Task.Run(() => worker())));
void worker() {
Interlocked.Increment(ref threadsReadyCount);
if (threadsReadyCount == threadCount)
threadsReady.Set();
else
threadsReady.WaitOne();
// Do some work...
}
考虑代码:
class TestClass
{
private bool _someFlag;
private object _sharedObject = new object();
private readonly object _syncObject = new object();
public object Read()
{
//lock (_syncObject)
{
_someFlag = false;
return _sharedObject;
}
}
public void Write(object obj)
{
//lock (_syncObject)
{
_someFlag = true;
_sharedObject = obj;
}
}
}
它有竞争条件问题。当我们调用 Read()
时,某些线程可以在 _someFlag = false;
和 return _sharedObject;
行之间调用 Write()
。我将通过 lock
操作员解决问题。但是你能帮我对这个竞争条件问题进行单元测试吗?
我不想出于测试目的或类似目的将 _someFlag
更改为 public
。
我想做这样的事情:
[Fact]
public void RaceConditionTest()
{
var correctObject = new object();
var test = new TestClass();
for (int i = 0; i < 1000; i++)
{
test.Write(correctObject);
var assertTask = Task.Run(() =>
{
var actualObj = test.Read();
Assert.True(object.ReferenceEquals(correctObject, actualObj), $"Failed on {i} iteration");
});
//Thread.Sleep(50);
var failTask = Task.Run(() => test.Write(new object()));
Task.WaitAll(assertTask, failTask);
}
}
但是我如何确定 assertTask
会在 failTask
之前启动?或者也许还有另一种方法可以对这种情况进行单元测试?提前致谢。
您可以在启动 failTask 之前检查 assertTask 是否 运行 或已完成:
while (assertTask.Status != Running && assertTask.Status != RanToCompletion)
Thread.Sleep(50);
我一直坚持这种方法。但仍在寻找更好的方法......
此测试在某些迭代中失败,但如果您取消注释 lock
运算符,测试将通过。
class TestClass
{
private IEventRecorder _eventRecorder;
private bool _someFlag;
private object _sharedObject = new object();
private readonly object _syncObject = new object();
#if DEBUG
public void SetEventRecorder(IEventRecorder eventRecorder) => _eventRecorder = eventRecorder;
#endif
public object Read()
{
//lock (_syncObject)
{
#if DEBUG
_eventRecorder?.Record(nameof(Read));
#endif
_someFlag = false;
return _sharedObject;
}
}
public void Write(object obj)
{
//lock (_syncObject)
{
#if DEBUG
_eventRecorder?.Record(nameof(Write));
#endif
_someFlag = true;
_sharedObject = obj;
}
}
public interface IEventRecorder
{
void Record(string eventName);
}
}
public class TestClassTests
{
private class EventRecorder : TestClass.IEventRecorder
{
private string _events = string.Empty;
public void Record(string eventName) => _events += eventName;
public string Events => _events;
public void Reset() => _events = string.Empty;
}
[Fact]
public void RaceConditionTest()
{
var correctObject = new object();
var eventRecorder = new EventRecorder();
var test = new TestClass();
test.SetEventRecorder(eventRecorder);
for (int i = 0; i < 1000; i++)
{
test.Write(correctObject);
var assertTask = Task.Run(() =>
{
var actualObj = test.Read();
if (eventRecorder.Events.StartsWith("WriteRead"))
Assert.True(object.ReferenceEquals(correctObject, actualObj), $"Failed on {i} iteration");
});
var failTask = Task.Run(() => test.Write(new object()));
Task.WaitAll(assertTask, failTask);
eventRecorder.Reset();
}
}
}
要测试并发问题,最好让所有线程在定义的时间点等待,然后全部释放。
var threadCount = someList.Length;
var threadsReady = new ManualResetEvent(false);
var threadsReadyCount = 0;
Task.WhenAll(someList.Select(item => Task.Run(() => worker())));
void worker() {
Interlocked.Increment(ref threadsReadyCount);
if (threadsReadyCount == threadCount)
threadsReady.Set();
else
threadsReady.WaitOne();
// Do some work...
}