所以我是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); }
现在,我向Presenter添加了SynchronizationContext线程后,我尝试在我的Fake View上为StatusMessage设置AutoResetEvent,但是当我运行测试时,SynchronizationContext.Current为null.我意识到我在新的Presenter中使用的线程模型并不完美,但这是测试多线程的正确技巧吗?为什么我的SynchronizationContext.Current为null?我该怎么做呢?
线程后的演示者
public class FtpPresenter : IFtpPresenter { ... void _view_GetFilesClicked(object sender,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 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不存在,则可能需要将赋值逻辑移动到Current并进入延迟加载.