有问题的代码
public void StartPlaying()
{
ThreadPool.QueueUserWorkItem(ignoredState =>
{
while (_playlist.Count > 0)
{
var audioFile = _playlist.Dequeue();
if (StartedPlaying != null)
StartedPlaying(this, new TypedAudioFileEventArgs(audioFile));
audioFile.SoundPlayer.PlaySync();
audioFile.SoundPlayer.Dispose();
if (StoppedPlaying != null)
StoppedPlaying(this, new TypedAudioFileEventArgs(audioFile));
}
});
}
和我的测试:
[TestMethod()]
public void StartPlayIsCalledTwice_OnlyRunningOnce()
{
int timeBetweenPlays = 0;
var target = new TypedAudioFilePlayer(timeBetweenPlays);
target.AddFile(TypedAudioFileCreator.CreateWord(1, "bl"));
target.StartedPlaying += StartedPlaying_Once;
target.StartPlaying();
target.StartPlaying();
}
private bool _once = false;
private void StartedPlaying_Once(object sender, TypedAudioFileEventArgs e)
{
if (!_once)
_once = true;
else
Assert.Fail("Should not be called more than once!");
}
我相信我的单元测试应该会失败,从ThreadPool.QueueUserWorkItem
的MSDN描述来判断:
对执行方法进行排队。该方法在线程池线程可用时执行。
默认的ThreadPool大小为512,因此应立即有两个线程可用于处理StartPlaying调用。我相信我的代码应该失败,因为我没有提供任何竞争条件的保障,两个线程都可以访问相同的资源。
这里发生了什么?
答案 0 :(得分:3)
因为只有在有要播放的项目时调用StartPlaying才会引发StartedPlaying
事件。
_playlist.Dequeue();
将您排队的文件出列。因此,第二次到达while (_playlist.Count > 0)
时,它会立即失败,直接通过第二次调用StartPlaying
而不会引发事件。
另外,正如Bruno Silva指出的那样,第二次调用StartPlaying
时产生的线程可能没有机会在测试退出之前执行任何操作。
对于它的价值,此代码中还有大约一百万至少2个线程错误:
// Where did _playlist come from? Is it shared state among the player threads?
// If so, all access to it should be in locks, since queues are not thread safe
while (_playlist.Count > 0)
// Both of these start threads and then immediately return.
// The test will probably exit before either of those threads do anything much
target.StartPlaying();
target.StartPlaying();
}
如果要进行适当的单元测试,则需要定义前置条件,期望,操作和后置条件:
前提条件:您有一个已初始化的TypedAudioFilePlayer,其中一个文件已排队:
var target = new TypedAudioFilePlayer(timeBetweenPlays);
target.AddFile(TypedAudioFileCreator.CreateWord(1, "bl"));
期望:如果两次调用StartPlaying,则只会引发一次StartedPlaying事件
target.StartedPlaying += StartedPlaying_Once;
操作: StartPlaying方法将被调用两次:
target.StartPlaying();
target.StartPlaying();
后置条件: StartedPlaying
事件只提出一次:
private bool _once = false;
private void StartedPlaying_Once(object sender, TypedAudioFileEventArgs e)
{
if (!_once)
_once = true;
else
Assert.Fail("Should not be called more than once!");
}
现在,您的测试成功了。在这种情况下,这并不好,因为我在上面解释了。您需要通过消除队列错误和竞争条件来使测试进入失败状态,然后努力使测试通过正确的方式。
答案 1 :(得分:1)
您似乎正在使用两个线程之间的共享资源,因此在第二次调用Play时可能不会将其设置为true。您可以使用锁来允许一次一个线程执行部分代码:
private readonly object lock_object=new object();
private void StartedPlaying_Once(object sender, TypedAudioFileEventArgs e)
{
lock(lock_object)
{
if (!_once)
_once = true;
else
Assert.Fail("Should not be called more than once!");
}
}
答案 2 :(得分:0)
那些是否会在文本执行之外失败?您的测试在排队项目后立即结束,因此当测试方法结束执行时,我不确定这些线程发生了什么。您是否尝试过使用WaitHandle
等待他们在测试中完成?
有关示例,请参阅http://msdn.microsoft.com/en-us/library/system.threading.waithandle.aspx。