2008-12-11 68 views
7

所以我是TDD的新手,并且我使用MVP模式成功创建了一个很好的小示例应用程序。我当前的解决方案的主要问题是它阻塞UI线程,所以我试图设置Presenter使用SynchronizationContext.Current,但是当我运行我的测试时,SynchronizationContext.Current为null。TDD测试重构以支持多线程

主持人之前线程

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线程后给演示之前,我试图设置的AutoResetEvent上我的假观为StatusMessage,但是当我运行测试SynchronizationContext.Current为空。我意识到我在新的Presenter中使用的线程模型并不完美,但这是测试多线程的正确技术吗?为什么我的SynchronizationContext.Current为空?我该怎么做呢?

演示线程

[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(); 
     } 
    } 
    ... 
} 
+0

好问题!我正在尝试解决类似的问题! – 2010-10-10 16:45:34

回答

3

我碰到类似的问题与ASP.NET MVC它在哪里的HttpContext线程

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; 
     } 
    } 
    ... 
} 

测试后那是缺失的。你可以做的一件事是提供一个备用构造函数,允许你注入一个模拟的SynchronizationContext或者公开一个做同样事情的公共setter。如果您无法在内部更改SynchronizationContext,请在默认构造函数中设置一个属性,并将该属性设置为SynchronizationContext.Current,并在整个代码中使用该属性。在你的替代构造函数中,你可以将模拟上下文分配给属性 - 或者如果你给它一个公共setter,你可以直接赋值给它。

public class 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类的接口,而不是直接在主题。我不认为你的单元测试应该创建新的线程,而是与一个模拟类进行交互,以确保创建线程的正确方法被调用。您也可以注入该依赖项。

如果在调用构造函数时SynchronizationContext.Current不存在,则可能需要将赋值逻辑移至getter并执行延迟加载。

+0

我将在我的测试中使用什么替代SynchronizationContext.Current?你有任何代码示例? – bendewey 2008-12-11 20:35:35

1

您的演示者必须具备很多应用程序逻辑。我会隐藏具体模型中的上下文和线程,并单独测试功能。