可测试的WinForms应用程序
如今,由于WPF的日益普及,WinForms应用程序主要属于旧代码。当一个团队决定全新桌面应用程序的开发堆栈时,他们主要投票支持WPF。另一方面,当需要升级与WinForms紧密结合的现有软件时,需要WinForms应用程序,以及对更高性能的要求等。

不同的方法
关于可测试WinForms应用程序的话题有很多意见。有人声称不可能测试WinForms应用程序,因为用户事件和业务逻辑之间存在很多依赖关系。
标准Windows窗体包含Designer.cs子类和包含用于用户操作的事件处理程序的子类。因此,如果我们遵循相同的原理并尝试在事件处理程序中实现应用程序逻辑,我们可以得出结论,为这种应用程序编写测试将非常困难。但是,如果我们从远处看这个问题,我们可以得出结论,每个具有用户界面的应用程序都可以表示为3个主要组件之间的交互:数据,用户界面和业务逻辑。
这是MV*模式的基本思想。如果我们成功地分离了这三个组件,那么我们的应用程序将是可测试的好方法。我使用MVP模式制作了一个简单的WinForms应用程序。可以从提供的链接下载整个项目。
在本文中,我将仅使用代码片段来大致了解代码的外观。
但是首先,关于MVP模式的几句话
MVP代表Model-View-Presenter。模型是包含数据的组件。它只是我们表格的数据持有人。视图表示用户界面。它包含我们表格的设计说明。Presenter是一个组件,可以完成WinForms应用程序中的大部分工作。它订阅了查看事件,这些事件是用户与表单交互(单击按钮,更改文本,选择更改等)以及操作系统与表单交互(加载,显示,绘制等)的结果。演示者需要处理所有这些事件,并在对其进行处理后对视图进行适当的操作。
代码结构
让我们从视图组件开始。
public interface IProductView { event EventHandler ViewLoad; event EventHandler<ProductViewModel> AddNewProduct; event EventHandler<ProductViewModel> ModifyProduct; event EventHandler<int> DeleteProduct; event EventHandler<int> ProductSelected; void PopulateDataGridView(IList<Product> products); void ClearInputControls(); void ShowMessage(string message); }public partial class Products : Form, IProductView { ... }1234567891011121314151617复制代码类型:[cpp]
我们创建了一个界面IProductView,该界面定义了Presenter和用户界面组件将如何交互的规则。
现在,让我们看一下演示者组件。
public class ProductPresenter{ private IProductView view; private IProductDataAccess dataAccesService; public ProductPresenter(IProductView view, IProductDataAccess dataAccesService) { this.view = view; this.dataAccesService = dataAccesService; SubsribeToViewEvents(); } private void SubsribeToViewEvents() { view.ViewLoad += View_Load; view.AddNewProduct += View_AddNewProduct; view.ProductSelected += View_ProductSelected; view.ModifyProduct += View_ModifyProduct; view.DeleteProduct += View_DeleteProduct; } ... }12345678910111213141516171819202122复制代码类型:[cpp]
我们的演示者IProductView在其构造函数中采用接口。这样,我们的视图(窗体)可以很容易地用实现相同接口的另一个视图替换。另外,在进行模拟时,我们可以轻松地通过构造函数注入此依赖关系。另一个组件是IProductDataAccess代表数据库接口。
public interface IProductDataAccess { IList<Product> GetAllProducts(); Product GetProduct(int id); bool AddProduct(Product product); bool DeleteProduct(int productId); bool EditProduct(int productId, Product product); string ErrorMessage { get; } }12345678910复制代码类型:[cpp]
一个测试用例示例
这个测试案例展示了我们如何轻松地在Presenter组件中模拟外部依赖关系。
[Test] public void ExpectToCallAddProductOnAppropriateEventReceived() { IProductView view = Substitute.For<IProductView>(); IProductDataAccess dataAccess = Substitute.For<IProductDataAccess>(); ProductPresenter presenter = new ProductPresenter(view, dataAccess); ProductViewModel viewModel = new ProductViewModel() { NameText = "Test", PriceText = "2" }; view.AddNewProduct += Raise.Event<EventHandler<ProductViewModel>>(view, viewModel); dataAccess.Received().AddProduct(Arg.Is<Product>(x=>x.Price == 2 && x.Name == "Test")); }1234567891011121314151617复制代码类型:[cpp]
结论
这只是一个从抽象开始的体系结构有多强大的简单示例。最好从更高级别的某些组件开始,然后定义接口,定义规则,这些组件将如何相互交互。
您可以从提供的链接下载带有更多测试的整个项目,也可以从此处的Github存储库中获取它,并根据需要提供帮助。