我有一个外部库,它有一个在后台线程上执行长时间运行任务的方法。完成后,它会触发启动方法的线程上的Completed事件(通常是UI线程)。它看起来像这样:
public class Foo
{
public delegate void CompletedEventHandler(object sender, EventArgs e);
public event CompletedEventHandler Completed;
public void LongRunningTask()
{
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
bw.RunWorkerAsync();
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(5000);
}
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (Completed != null)
Completed(this, EventArgs.Empty);
}
}
调用此库的代码如下所示:
private void button1_Click(object sender, EventArgs e)
{
Foo b = new Foo();
b.Completed += new Foo.CompletedEventHandler(b_Completed);
b.LongRunningTask();
Debug.WriteLine("It's all done");
}
void b_Completed(object sender, EventArgs e)
{
// do stuff
}
如果在事件中返回数据,我如何对.LongRunningTask的调用进行单元测试?
答案 0 :(得分:2)
嗯,我相信BackgroundWorker
使用当前的SynchronizationContext
。您可以实现自己的SynchronizationContext
子类以允许更多控制(甚至可能在同一个线程上运行代码,尽管这会破坏依赖在其他线程中运行的)并在运行测试之前调用SetSynchronizationContext
。
您需要在测试中订阅该事件,然后检查您的处理程序是否被调用。 (Lambda表达式对此很有用。)
例如,假设您有一个SynchronizationContext
,只允许您在需要时运行所有工作,并告诉您何时完成,您的测试可能会:
LongRunningTask()
诚然,这有点令人讨厌。如果可以只是同步测试它正在进行的工作,那将会更容易。
答案 1 :(得分:2)
我不确定我是否做对了。如果它触发事件,你想检查外部库吗?或者你想检查你是否在事件被触发时做了什么?
如果是后者,我会使用模拟。但问题是,您的代码似乎很难测试,因为您在用户界面中执行逻辑操作。尝试写一个“被动”视图,让主持人做出魔术。例如,使用模型视图展示器模式http://msdn.microsoft.com/en-us/magazine/cc188690.aspx
整个事情就像这样。
模特
public class Model : IModel
{
public event EventHandler<SampleEventArgs> Completed;
public void LongRunningTask()
{
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += this.bw_DoWork;
bw.RunWorkerCompleted += this.bw_RunWorkerCompleted;
bw.RunWorkerAsync();
}
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (this.Completed != null)
{
this.Completed(this, new SampleEventArgs {Data = "Test"});
}
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
System.Threading.Thread.Sleep(5000);
}
}
视图
public Form1()
{
InitializeComponent();
}
public event EventHandler Button1Clicked;
public void Update(string data)
{
this.label1.Text = data;
}
private void Button1Click(object sender, EventArgs e)
{
if (this.Button1Clicked != null)
{
this.Button1Clicked(this, EventArgs.Empty);
}
}
演示者
public class Presenter
{
private readonly IForm1 form1;
private readonly IModel model;
public Presenter(IForm1 form1, IModel model)
{
this.form1 = form1;
this.model = model;
this.form1.Button1Clicked += this.Form1Button1Clicked;
this.model.Completed += this.ModelCompleted;
}
private void ModelCompleted(object sender, SampleEventArgs e)
{
this.form1.Update(e.Data);
}
private void Form1Button1Clicked(object sender, EventArgs e)
{
this.model.LongRunningTask();
}
}
你组装它的地方(例如在Program类中)
var form = new Form1();
var model = new Model();
var presenter = new Presenter(form, model);
Application.Run(form);
然后您可以轻松地在单元测试中测试演示者。 gui中的部分现在已经不足以进行测试了。
可能的测试看起来像这样
[Test]
public void Test()
{
var form1Mock = new Mock<IForm1>();
var modelMock = new Mock<IModel>();
var presenter = new Presenter(form1Mock.Object, modelMock.Object);
modelMock.Setup(m => m.LongRunningTask()).Raises(m => m.Completed += null, new SampleEventArgs() { Data = "Some Data" });
form1Mock.Raise(f => f.Button1Clicked += null, EventArgs.Empty);
form1Mock.Verify(f => f.Update("Some Data"));
}
答案 2 :(得分:1)
您可以创建一个有助于将其转换为同步调用的扩展方法。您可以进行调整,例如使其更通用并传递超时变量,但至少它会使单元测试更容易编写。
static class FooExtensions
{
public static SomeData WaitOn(this Foo foo, Action<Foo> action)
{
SomeData result = null;
var wait = new AutoResetEvent(false);
foo.Completed += (s, e) =>
{
result = e.Data; // I assume this is how you get the data?
wait.Set();
};
action(foo);
if (!wait.WaitOne(5000)) // or whatever would be a good timeout
{
throw new TimeoutException();
}
return result;
}
}
public void TestMethod()
{
var foo = new Foo();
SomeData data = foo.WaitOn(f => f.LongRunningTask());
}
答案 3 :(得分:1)
为了测试异步代码,我使用了类似的帮助器:
public class AsyncTestHelper
{
public delegate bool TestDelegate();
public static bool AssertOrTimeout(TestDelegate predicate, TimeSpan timeout)
{
var start = DateTime.Now;
var now = DateTime.Now;
bool result = false;
while (!result && (now - start) <= timeout)
{
Thread.Sleep(50);
now = DateTime.Now;
result = predicate.Invoke();
}
return result;
}
}
在测试方法中,然后调用类似的东西:
Assert.IsTrue(AsyncTestHelper.AssertOrTimeout(() => changeThisVarInCodeRegisteredToCompletedEvent, TimeSpan.FromMilliseconds(500)));