gpt4 book ai didi

multithreading - TDD 测试重构以支持多线程

转载 作者:行者123 更新时间:2023-12-04 03:38:21 26 4
gpt4 key购买 nike

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

线程前的演示者

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

线程后的演示者
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);
}

假 View
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。您可以做的一件事是提供一个备用构造函数,它允许您注入(inject)一个模拟 SynchronizationContext 或公开一个做同样事情的公共(public) setter。如果您无法在内部更改 SynchronizationContext,则在默认构造函数中创建一个设置为 SynchronizationContext.Current 的属性,并在整个代码中使用该属性。在您的备用构造函数中,您可以将模拟上下文分配给属性——或者如果您给它一个公共(public) setter ,您可以直接分配给它。

公共(public)类 FtpPresenter : IFtpPresenter
{
公共(public) 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 类的接口(interface),而不是直接依赖于 Thread。我不认为您的单元测试应该创建新线程,而是与模拟类进行交互,以确保调用创建线程的正确方法。您也可以注入(inject)该依赖项。

如果在调用构造函数时 SynchronizationContext.Current 不存在,您可能需要将 Current 的赋值逻辑移动到 getter 中并进行延迟加载。

关于multithreading - TDD 测试重构以支持多线程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/360753/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com