C# 关于Windows窗体应用程序单元测试的建议

C# 关于Windows窗体应用程序单元测试的建议,c#,winforms,unit-testing,C#,Winforms,Unit Testing,我已经编写了一个Windows窗体应用程序,现在我想为它编写一些单元测试(不完全是测试驱动开发,因为我是在开发之后编写测试的,但最好是晚些时候才编写),我的问题是,有了这样一个应用程序,如何编写单元测试,考虑到几乎所有的方法和事件都是私有的?我听说过NUnit表单,但我听说了它的好的和坏的方面,而且这个项目已经有一段时间没有真正的开发了,所以看起来被放弃了。此外,如果我为用户通过点击/按下按钮触发的所有事件编写单元测试用例,或者我必须为所有方法编写单元测试用例,并找出一种测试我的私有方法的方法,

我已经编写了一个Windows窗体应用程序,现在我想为它编写一些单元测试(不完全是测试驱动开发,因为我是在开发之后编写测试的,但最好是晚些时候才编写),我的问题是,有了这样一个应用程序,如何编写单元测试,考虑到几乎所有的方法和事件都是私有的?我听说过NUnit表单,但我听说了它的好的和坏的方面,而且这个项目已经有一段时间没有真正的开发了,所以看起来被放弃了。此外,如果我为用户通过点击/按下按钮触发的所有事件编写单元测试用例,或者我必须为所有方法编写单元测试用例,并找出一种测试我的私有方法的方法,那么人们是否普遍认为项目已经有了足够的单元测试


编辑:我的业务逻辑与表示逻辑分离,我的业务逻辑公开了1或2个公共方法,因此表单可以访问它们,但是业务逻辑中的所有私有方法呢?

我要做的第一件事是确保业务逻辑与表单之间有适当的分离。基本上,使用一种模式。然后,您可以轻松地测试表单之外的所有内容,就好像表单根本不存在一样


现在,这仍然会留下一些未经测试的表单特定功能。即,表单是否正确连接到服务?为此,您仍然可以考虑类似NUnt窗体或其他替代方案。

将所有业务逻辑拆分为单独的项目和单元测试。或者至少将所有逻辑从表单移动到单独的类中。

您有几个选项

  • 使用编码UI之类的工具通过用户界面进行测试。这不是一个很好的选择,因为它比单元测试慢,而且测试往往更脆弱

  • 将业务逻辑与表示逻辑分开。如果您的UI中有很多执行业务逻辑的私有方法,那么您已经将业务逻辑与演示紧密耦合。开始识别它们,并将它们移动到具有公共接口的单独类中,以便进行测试。阅读一些原则,它们可以帮助您保持代码松散耦合和可测试性


  • 单元测试图形应用程序的关键是确保所有业务逻辑都在一个单独的类中,而不是在代码后面

    在设计这样一个系统时,设计模式喜欢并且可以提供帮助

    举个例子:

    public partial class Form1 : Form, IMyView
    {
        MyPresenter Presenter;
        public Form1()
        {
            InitializeComponent();
            Presenter = new MyPresenter(this);
        }
    
        public string SomeData
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                MyTextBox.Text = value;
            }
        }
    
        private void button1_Click(object sender, EventArgs e)
        {
            Presenter.ChangeData();
        }
    }
    
    public interface IMyView
    {
        string SomeData { get; set; }
    }
    
    public class MyPresenter
    {
        private IMyView View { get; set; }
        public MyPresenter(IMyView view)
        {
            View = view;
            View.SomeData = "test string";
        }
    
        public void ChangeData()
        {
            View.SomeData = "Some changed data";
        }
    }
    
    正如您所看到的,表单只有一些基础结构代码,可以将所有内容组合在一起。所有逻辑都在Presenter类中,该类只知道视图接口

    如果您想对此进行单元测试,可以使用模拟工具(如模拟视图界面)并将其传递给演示者

    [TestMethod]
    public void TestChangeData()
    {
        IMyView view = MockRepository.DynamickMock<IMyView>();
        view.Stub(v => v.SomeData).PropertyBehavior();
    
        MyPresenter presenter = new MyPresenter(view);
    
        presenter.ChangeData();
    
        Assert.AreEqual("Some changed data", view.SomeData);
    }
    
    [TestMethod]
    public void TestChangeData()
    {
    IMyView视图=MockRepository.DynamickMock();
    view.Stub(v=>v.SomeData.PropertyBehavior();
    MyPresenter presenter=新建MyPresenter(视图);
    presenter.ChangeData();
    Assert.AreEqual(“一些更改的数据”,view.SomeData);
    }
    
    使用approvaltests(www.approvaltests.com或nuget)进行单元测试,该视图非常简单。这里有一段视频:

    然而,似乎您也担心为了能够测试功能而将函数设置为默认或公共

    这些通常被称为接缝;进入代码进行测试的方法。 它们很好。有时,人们把私有/公共与安全混淆起来,害怕将私有函数变成公共函数,但反射会调用这两个函数,因此它并不真正安全。其他时候,人们担心类的API接口。但这只在你有一个公共API的情况下才重要,如果你有一个winform应用程序,它可能是最高级的(没有其他消费者调用它)

    您是程序员,因此可以设计易于测试的代码。这通常意味着只需更改几个public方法,并创建几个允许传递依赖项的connivence方法

    例如:

    buttonclick += (o,e)=> {/*somecode*/};
    
    这很难测试

    private void button1_Click(object sender, EventArgs e) {/*somecode*/}
    
    仍然很难测试

    public void button1_Click(object sender, EventArgs e) {/*somecode*/}
    
    更容易测试

    private void button1_Click(object sender, EventArgs e) { DoSave();}
    public void DoSave(){/*somecode*/}
    
    真的很容易测试

    如果您需要从活动中获得一些信息,则此选项将加倍。即

    public void ZoomInto(int x, int y)
    
    测试相应的鼠标点击事件要容易得多,传递调用仍然可以是一条可忽略的线。

    可以使用带有Reactive.UI的MVVM(Model–View–ViewModel)模式来编写可测试的WinForms代码。要实现关注点的分离,确实需要。请参阅:Reactive.UI使用Winforms/MVVM/Reactive.UI的主要缺点是没有太多的示例(针对Winforms)。好处是它适用于几乎所有的桌面框架和语言。你只为一个人学习,但原则适用于所有人。当你有很多私有方法时,这没关系。IMHO:尝试使用公共方法开始您想要测试的业务流程。你可以使用告诉-不要问:并且仍然保持所有这些方法的私密性

    你也可以通过驱动UI来测试代码,但这并不是非常推荐的,因为最终的测试(1)非常脆弱,(2)更难工作,而且IMHO,(3)不能以与纯代码测试相同的精细度编写;(4)最后:如果你使用一个数据库,你需要考虑用测试数据填充它,并且因为你的数据库在每个测试之前都必须处于一个干净、明确的状态,(5)当你重新初始化每个测试的数据时,你的测试可能比你的想法慢得多。
    小结:编写具有良好SoC的代码(例如,通过应用MVVM),那么您的代码将具有更好的可测试性。

    好的,我有,但是如果我的表单访问的业务逻辑中有1或2个公共方法,这些方法反过来访问总线中的私有方法