TDDテストリファクタリングはマルチスレッドをサポートするために
-
21-08-2019 - |
質問
だから私はTDDに初心者だし、私は正常にMVPパターンを使用して素晴らしい小さなサンプルアプリケーションを作成しました。私の現在のソリューションの主要な問題は、そのは、UIスレッドをブロックし、だから私はプレゼンターが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です。私は私の新しいプレゼンターで私が使用しているスレッドモデルが完璧ではないが、これはテストマルチスレッドのための右の技術であることを認識しますか?なぜ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
};
// 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();
}
}
...
}
解決
私はそれが欠けているのHttpContextでASP.NET MVCで同様の問題に実行しました。あなたができることの一つは、あなたがモックのSynchronizationContextを注入または同じことを行い、公共セッターを公開することを可能にする別のコンストラクタを提供しています。あなたが内部のSynchronizationContextを変更できない場合は、デフォルトのコンストラクタでSynchronizationContext.Currentに設定プロパティを作成し、あなたのコード全体でそのプロパティを使用します。あなたの別のコンストラクタでは、プロパティにモックコンテキストを割り当てることができます - またはあなたがそれを公共のセッターを与える場合、あなたはそれに直接割り当てることができます。
。パブリッククラスFtpPresenter:IFtpPresenter { 公共のSynchronizationContext CurrentContext {取得します。セットする; }
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が存在しない場合は、、あなたはゲッターに現在に割り当てロジックを移動し、遅延ロードを実行する必要があります。
他のヒント
あなたがプレゼンターに多くのアプリロジックを持っています。私は具体的なモデル内部のコンテキストとスレッドを隠し、単独の機能をテストします。