TDD测试重构以支持多线程
所以我是TDD的新手,我成功地使用MVP模式创建了一个很好的小示例应用程序。 我当前的解决方案的主要问题是它阻塞UI线程,所以我试图设置Presenter使用SynchronizationContext.Current,但是当我运行我的测试时,SynchronizationContext.Current为null。
线程前的演示者
public class FtpPresenter : IFtpPresenter
{
...
void _view_GetFilesClicked(object sender, EventArgs e)
{
_view.StatusMessage = Messages.Loading;
try
{
var settings = new FtpAuthenticationSettings()
{
Site = _view.FtpSite,
Username = _view.FtpUsername,
Password = _view.FtpPassword
};
var files = _ftpService.GetFiles(settings);
_view.FilesDataSource = files;
_view.StatusMessage = Messages.Done;
}
catch (Exception ex)
{
_view.StatusMessage = ex.Message;
}
}
...
}
线程前的测试
[TestMethod]
public void Can_Get_Files()
{
var view = new FakeFtpView();
var presenter = new FtpPresenter(view, new FakeFtpService(), new FakeFileValidator());
view.GetFiles();
Assert.AreEqual(Messages.Done, view.StatusMessage);
}
在向演示程序添加SynchronizationContext线程后,我尝试在StatusMessage的虚假视图上设置AutoResetEvent,但是当我运行测试时,SynchronizationContext.Current为null。 我意识到我在新的Presenter中使用的线程模型并不完美,但这是测试多线程的正确技术吗? 为什么我的SynchronizationContext.Current为空? 我该怎么做呢?
演讲者在线程之后
public class FtpPresenter : IFtpPresenter
{
...
void _view_GetFilesClicked(object sender, EventArgs e)
{
_view.StatusMessage = Messages.Loading;
try
{
var settings = new FtpAuthenticationSettings()
{
Site = _view.FtpSite,
Username = _view.FtpUsername,
Password = _view.FtpPassword
};
// Wrap the GetFiles in a ThreadStart
var syncContext = SynchronizationContext.Current;
new Thread(new ThreadStart(delegate
{
var files = _ftpService.GetFiles(settings);
syncContext.Send(delegate
{
_view.FilesDataSource = files;
_view.StatusMessage = Messages.Done;
}, null);
})).Start();
}
catch (Exception ex)
{
_view.StatusMessage = ex.Message;
}
}
...
}
穿线后测试
[TestMethod]
public void Can_Get_Files()
{
var view = new FakeFtpView();
var presenter = new FtpPresenter(view, new FakeFtpService(), new FakeFileValidator());
view.GetFiles();
view.GetFilesWait.WaitOne();
Assert.AreEqual(Messages.Done, view.StatusMessage);
}
假视图
public class FakeFtpView : IFtpView
{
...
public AutoResetEvent GetFilesWait = new AutoResetEvent(false);
public event EventHandler GetFilesClicked = delegate { };
public void GetFiles()
{
GetFilesClicked(this, EventArgs.Empty);
}
...
private List<string> _statusHistory = new List<string>();
public List<string> StatusMessageHistory
{
get { return _statusHistory; }
}
public string StatusMessage
{
get
{
return _statusHistory.LastOrDefault();
}
set
{
_statusHistory.Add(value);
if (value != Messages.Loading)
GetFilesWait.Set();
}
}
...
}
我遇到类似的问题与ASP.NET MVC,它是缺少的HttpContext。 你可以做的一件事是提供一个备用的构造函数,允许你注入一个模拟的SynchronizationContext或者公开一个做同样事情的公共setter。 如果您无法在内部更改SynchronizationContext,请在默认构造函数中设置一个属性,并将该属性设置为SynchronizationContext.Current,并在整个代码中使用该属性。 在你的替代构造函数中,你可以将模拟上下文分配给属性 - 或者如果你给它一个公共setter,你可以直接赋值给它。
公共类FtpPresenter:IFtpPresenter {public SynchronizationContext CurrentContext {get; 组; }
public FtpPresenter() : this(null) { }
public FtpPresenter( SynchronizationContext context )
{
this.CurrentContext = context ?? SynchronizationContext.Current;
}
void _view_GetFilesClicked(object sender, EventArgs e)
{
....
new Thread(new ThreadStart(delegate
{
var files = _ftpService.GetFiles(settings);
this.CurrentContext.Send(delegate
{
_view.FilesDataSource = files;
_view.StatusMessage = Messages.Done;
}, null);
})).Start();
...
}
我会做的另一个观察是,我可能会让您的演示者依赖于Thread类的接口,而不是直接在Thread上。 我不认为你的单元测试应该创建新的线程,而是与一个模拟类进行交互,以确保创建线程的正确方法被调用。 您也可以注入该依赖项。
如果在调用构造函数时SynchronizationContext.Current不存在,则可能需要将赋值逻辑移至getter并执行延迟加载。
演讲者必须掌握很多应用逻辑。 我会隐藏具体模型中的上下文和线程,并单独测试功能。
链接地址: http://www.djcxy.com/p/42123.html