【问题标题】:Advice on Unit testing a Windows Forms application对 Windows 窗体应用程序进行单元测试的建议
【发布时间】:2012-02-01 14:02:26
【问题描述】:

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

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

【问题讨论】:

    标签: c# winforms unit-testing


    【解决方案1】:

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

    Model View PresenterModel View Controller 等设计模式可以在设计这样的系统时提供帮助。

    举个例子:

    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 类中,它只知道一个视图接口。

    如果您想对此进行单元测试,您可以使用像 Rhino Mocks 这样的 Mocking 工具来模拟 View 界面并将其传递给您的演示者。

    [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);
    }
    

    【讨论】:

      【解决方案2】:

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

      现在,这仍然会留下一些未经测试的特定于表单的功能。即,表单是否正确连接到服务?为此,您仍然可以考虑使用 NUnit Forms 或其他替代方法。

      【讨论】:

      • 好的,我有,但是如果我的业务逻辑中有 1 或 2 个公共方法可供我的表单访问,而这些方法又访问我的业务逻辑中的私有方法,那么测试公共方法是否足够在业务逻辑中与我的表单交互?并且不编写直接测试我的任何私有方法的单元测试?
      • @Jim - 是的,这被我的许多人认为是可以接受的,因为测试公共方法是在私有方法中执行代码。 (这与删除私有方法并将其代码移动到公共方法中的内部块实际上并没有太大区别。)
      • MVC 本身并不能保证测试他想要测试的东西。他可以实现 MVC 模式,但如果控制器中的业务逻辑在 Form 项目内部,它们仍然是内部的。如果他将其移至外部项目,那么他想要的内部项目现在将是公开的,因此即使没有什么是完全保密的,也不再“受保护”。
      【解决方案3】:

      将所有业务逻辑分解为一个单独的项目并对其进行单元测试。或者至少将表单中的所有逻辑移到单独的类中。

      【讨论】:

        【解决方案4】:

        您有几个选择。

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

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

        【讨论】:

        • 至于#1:它不仅不是一个很好的选择,因为它很慢,它甚至也不是单元测试。 UI 测试就是这样,它更接近于集成测试。至于#2:不仅如果您有“很多”私有方法,而且如果您有执行业务逻辑的任何私有方法,您是否已经耦合了您的代码。耦合就是耦合,不应该有尺度。除此之外,#2 完全正确。
        【解决方案5】:

        使用approvaltests(www.approvaltests.com 或nuget)对视图进行单元测试非常简单。这里有一个视频:http://www.youtube.com/watch?v=hKeKBjoSfJ8

        但是,您似乎也担心为了能够测试功能而将函数设为默认或公开。

        这些通常被称为接缝;进入代码进行测试的方法。 他们很好。有时人们将私有/公共与安全性相混淆,并且害怕将私有函数公开,但反射会调用其中任何一个,因此它并不真正安全。其他时候,人们担心类的 API 接口。但这只有在你有一个公共 API 时才重要,如果你有一个 winform 应用程序,它可能意味着是顶级的(没有其他消费者调用它。)

        您是程序员,因此可以将您的代码设计为易于测试。这通常意味着改变一些公共方法并创建一些允许传递依赖关系的便利方法。

        例如:

        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)
        

        更容易测试对应的鼠标点击事件,并且透传调用仍然可以是一个可忽略的单行。

        【讨论】:

        • 有些人混淆了为什么不好的原因可以忽略不计的原因与做正确的事情的原因。封装不仅仅是为了安全。编码人员认为可以将潜在的封装成员公开的时刻就是他们失去关注点分离和可靠的开发实践的时刻。现在,您的应用程序可能没有失去安全性,但维护起来并不容易,并且采用变通方法进行测试,而不是只做正确的事。
        • 当私有公开时,事情就更难维护了...我经常想知道这将如何适用于所有没有公共/私有的语言,例如 ruby​​/javascript/etc...
        • 在选择一种语言时,您考虑的不仅仅是它可以运行的硬件,以及代码库维护者的优势。您还考虑了语言的优势。 C# 的主要优势包括类型安全、垃圾收集、通用、友好的原生构造等。仅仅因为另一种语言(具有自己的优势)与对 C# 的评论不匹配并不意味着该评论是相关的。 Javascript 的主要优势在于它不是类型安全的。这对于它解决的问题是最好的。此外,您还可以在 javascript 中拥有私有成员。
        • 要点:如果您使用 C#,请使用 C# 面向对象技术进行编程。是的,您可以使用程序或非封装方法来廉价地解决问题。或者,由于您使用的是 C#,因此您可以找出 C# 的方法。不要误以为所有语言都是一样的。
        • -1。与上述 cmets 一样,这是一种不好的做法,并且很快会导致代码难以维护。纯粹为了测试而公开一个项目绝不是一个好主意。更好的选择是使用 internal 访问修饰符和 InternalsVisibleTo 属性将这些项目公开给您的测试。
        【解决方案6】:

        可以使用带有 Reactive.UI 的 MVVM(模型-视图-视图模型)模式来编写可测试的 WinForms 代码。要获得真正需要的关注点分离。请参阅:Reactive.UI https://reactiveui.net/ 使用 Winforms / MVVM / Reactive.UI 的主要缺点是没有很多使用示例(用于 WinForms)。好处是它适用于几乎所有桌面框架和语言。您为一个人学习它,但这些原则适用于所有人。当你有很多私有方法时,没关系。恕我直言:尝试使用公共方法开始您要测试的业务流程。您可以使用 tell-don't-ask:https://martinfowler.com/bliki/TellDontAsk.html 并仍然将所有这些方法保密。

        也可以通过驱动 UI 来测试代码,但不那么强烈建议这样做,因为结果测试 (1) 非常脆弱,(2) 更难工作,而且恕我直言,(3) 不可能编写在与纯代码测试相同的细粒度水平; (4) 最后:如果你使用数据库,你需要考虑用测试数据填充它,而且,因为在每次测试之前你的数据库必须处于干净、定义明确的状态,(5) 你的测试可能运行得更慢比您重新初始化每个测试的数据时的想法。

        总结:使用良好的 SoC 编写代码(例如通过应用 MVVM),那么您的代码将具有更好的可测试性。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-12-14
          • 1970-01-01
          • 2016-04-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-03-21
          相关资源
          最近更新 更多