加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

TDD测试重构以支持MultiThreading

发布时间:2020-12-13 20:11:37 所属栏目:百科 来源:网络整理
导读:所以我是TDD的新手,我使用MVP模式成功创建了一个不错的小样本应用程序.我当前解决方案的主要问题是它阻止了UI线程,所以我试图设置Presenter以使用SynchronizationContext.Current,但是当我运行我的测试时,SynchronizationContext.Current为null. 演讲者在线
所以我是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并进入延迟加载.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读