【问题标题】:First Unit Test (VS2010 C#)第一个单元测试(VS2010 C#)
【发布时间】:2011-02-15 08:29:46
【问题描述】:

这是我第一次接触单元测试,我试图了解如何将这个概念用于简单的日期验证。

用户可以选择一个 ToDate,它表示可以进行付款的日期。如果我们的日期无效,则无法付款。

    private void CheckToDate(DateTime ToDate)
    {
        if (Manager.MaxToDate < ToDate.Year)
            //show a custom message
    }

在这种情况下如何使用单元测试?

问候,

亚历克斯

感谢您的回答:

正如许多人所建议的那样,我将拆分函数并将验证与消息显示分开,并为此使用单元测试。

public bool IsDateValid(DateTime toDate)
{
    return (Manager.MaxToDate < toDate.Year);
}

【问题讨论】:

    标签: c# unit-testing visual-studio-2010 vs-unit-testing-framework


    【解决方案1】:

    是的,这是可能的。但是单元测试改变了你的类的设计。要对这段代码进行单元测试,您应该进行以下更改:

    1. 公开您的方法。 (可以使其受到保护,但为简单起见,将其公开)。

    2. 将此方法的所有外部依赖项提取到接口,以便您可以模拟它们。然后你可以使用一些模拟库(moqRhino.Mocks)来模拟真正的依赖并编写断言。

    3. 写测试。

    这里是示例代码。

    被测类:

    public class ClassUnderTest
    {
        public IManager Manager {get;set;}
        public IMessanger Messanger {get;set}
    
        public  ClassUnderTest (IManager manager, IMessanger messanger)
        {
            Manager = manager;
            Messanger = messanger;
        }
    
        private void CheckToDate(DateTime ToDate)
        {
            if (Manager.MaxToDate < ToDate.Year)
                //show a custom message
                Messanger.ShowMessage('message');
        }
    }
    

    测试:

    [TestFixture]
    public class Tester
    {
        public void MessageIsShownWhenDateIsLowerThanMaxDate()
        {
            //SetUp
            var manager = new Mock<IManager>();
            var messanger = new Mock<IMessanger>();
    
            var maxDate = DateTime.Now;
    
            manager.Setup(m => m.MaxToDate).Returns(maxDate);
    
            var cut = new ClassUnderTest (manager.Object, messanger.Object);
    
            //Act
            cut.CheckToDate();
    
            //Assert
            messanger.Verify(foo => foo.ShowMessage("message"), Times.AtLeastOnce())
        }
    }
    

    通过测试引入的设计更改为您提供了很好的系统解耦。并且可以为特定的类编写测试,而不是编写外部依赖项。

    【讨论】:

    • +1 - 基本上是相同的答案,但你很快就完成了 :) 并且使用了一个卡在那里的模拟库。干得好:)
    • 请记住,使用随时间变化的值(例如DateTime.Now)可能会根据运行测试的时间产生不同的结果。我通常会尝试使用硬编码的测试值来实现可预测性。
    • 你的方法 MessageIsShownWhenDateIsLowerThanMaxDate 应该有一个 [Test] 属性,被 NUnit 识别为测试方法。还有一个小问题... //Setup 注释通常称为 //Arrange,以便更容易记住 Arrange/Act/Assert。
    【解决方案2】:

    当然 :-) 检测显示自定义消息可能需要一些技巧(我假设您的意思是显示在 GUI 上的消息框,但即使消息显示不同,想法也是一样的)。

    您无法从单元测试中检测到消息框,也不想从单元测试中启动整个 GUI 环境。解决此问题的最简单方法是在单独的方法中隐藏显示消息框的实际代码,最好是在不同的界面中。然后你可以为你的单元测试注入这个接口的模拟实现。这个 mock 不显示任何内容,只是记录传递给它的消息,因此您可以在单元测试中检查它。

    另一个问题是你的方法是private。首先检查它是从哪里调用的,以及是否可以通过公共方法调用它而没有太多复杂性。如果没有,您可能需要(暂时)将其公开以启用单元测试。请注意,对私有方法进行单元测试的需求通常是一种设计气味:您的类可能试图做太多事情,承担了太多不同的职责。您可以将它的一些功能提取到一个不同的类中,在那里它变成公共的,因此可以直接进行单元测试。但首先您需要进行这些单元测试,以确保在重构​​时不会破坏任何内容。

    然后你需要在测试前设置Manager.MaxToDate一个合适的日期,并用各种参数调用CheckToDate,检查结果是否符合预期。

    类似技巧的推荐阅读是Working Effectively with Legacy Code

    【讨论】:

      【解决方案3】:

      最好在类的公共接口上进行单元测试。所以,我建议你要么公开它,要么间接测试它(通过你公开的公共方法)。

      至于“是否可以为这样的东西创建单元测试?”,这取决于您希望对单元测试的概念有多纯粹,您希望它们有多依赖于用户,以及究竟是什么@ 987654321@ 可以。

      你希望你的单元测试有多纯粹?如果您不在乎它们是否是肮脏的黑客,那么您可以使用反射将私有方法公开给您的单元测试,然后直接调用它。但是,这通常是一种不好的做法,因为根据定义,您的私有函数可能会发生变化。否则,您只需将它们公开。

      如果//show a custom message 打印到控制台,那么您可以相当轻松地进行静默运行测试。如果你真的想验证输出,你必须连接到你的Console.Out,这样你才能看到打印的内容,并添加相应的断言。

      如果 //show a custom message 使用 MessageBox.Show,那么您可能必须进行 UI 自动化测试才能对此进行测试。您的测试将无法在后台静默运行,并且如果您在测试运行时移动鼠标将会中断。

      如果您不想仅仅为了测试这个类的逻辑而进行 UI 自动化测试,那么我所知道的最好的方法是修改您的类以使用依赖注入。将所有实际输出代码 (MessageBox.Show) 封装到另一个类中,通过接口或抽象基类对其进行抽象,并使原始类引用抽象类型。这样你就可以在你的测试中注入一个 mock,它实际上不会输出到屏幕上。

      public interface INotification
      {
          void ShowMessage(string message);
      }
      
      public class MessageBoxNotification : INotification
      {
          public void ShowMessage(string message)
          {
              MessageBox.Show(message);
          }
      }
      
      public class MyClass
      {
          private INotification notification;
      
          public MyClass(INotification notification)
          {
              this.notification = notification;
          }
      
          public void SomeFunction(int someValue)
          {
              // Replace with whatever your actual code is...
              ToDate toDate = new SomeOtherClass().SomeOtherFunction(someValue);
              CheckToDate(toDate);
          }
      
          private void CheckToDate(DateTime ToDate)
          {
              if (Manager.MaxToDate < ToDate.Year)
                  notification.Show("toDate, too late!: " + toDate.ToString());
          }
      }
      

      您的单元测试将创建自己的自定义INotification 类,将其传递给MyClass 的构造函数,然后调用SomeFunction 方法。

      您可能想要抽象Manager 之类的东西,而这些类涉及以类似方式计算ToDate

      【讨论】:

        【解决方案4】:

        引入单元测试通常会让您更​​积极地思考代码设计(如果您还没有这样做的话)。你的案例在这方面很有趣。测试起来很棘手,其中一个原因是它做了两件不同的事情:

        • 它验证日期
        • 它通过显示一条消息对失败的验证做出反应

        精心设计的方法只做一件事。因此,我建议稍微重构一下代码,以便获得一个除了验证之外什么都不做的验证方法。这种方法测试起来会很简单:

        public bool IsDateValid(DateTime toDate)
        {
            // just guessing on the rules here...
            return (Manager.MaxToDate >= toDate.Year);
        }
        

        这也将使验证代码更加可重用,因为它将如何处理结果的决定转移到调用代码。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-04-18
          • 2012-03-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-05-22
          • 1970-01-01
          相关资源
          最近更新 更多