7

私は 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 Threading を Presenter に追加した後、StatusMessage の Fake View に 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();
        }
    }
    ...
}
4

2 に答える 2

3

欠落している HttpContext である ASP.NET MVC で同様の問題に遭遇しました。できることの 1 つは、モック SynchronizationContext を挿入できる代替コンストラクターを提供するか、同じことを行うパブリック セッターを公開することです。内部で SynchronizationContext を変更できない場合は、既定のコンストラクターで SynchronizationContext.Current に設定するプロパティを作成し、コード全体でそのプロパティを使用します。代替コンストラクターでは、モック コンテキストをプロパティに割り当てることができます。また、パブリック セッターを指定する場合は、直接割り当てることもできます。

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();

    ...
   }

私が行うもう 1 つの観察は、プレゼンターが Thread に直接依存するのではなく、Thread クラスへのインターフェイスに依存する可能性があるということです。単体テストで新しいスレッドを作成する必要はないと思いますが、スレッドを作成するための適切なメソッドが呼び出されることを保証するモック クラスとやり取りする必要があります。その依存関係を注入することもできます。

コンストラクターが呼び出されたときに SynchronizationContext.Current が存在しない場合は、Current への割り当てロジックを getter に移動し、遅延読み込みを実行する必要がある場合があります。

于 2008-12-11T20:31:39.000 に答える
1

プレゼンターには多くのアプリ ロジックが必要です。具体的なモデル内にコンテキストとスレッドを隠し、機能だけをテストします。

于 2008-12-18T19:04:52.997 に答える