2008-12-11 72 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

您的演示者必須具備很多應用程序邏輯。我會隱藏具體模型中的上下文和線程,並單獨測試功能。