首次测试(VS2010 C#)

时间:2011-02-15 08:29:47

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

这是我第一次遇到单元测试,我试图了解如何在简单的日期验证中使用这个概念。

用户可以选择代表日期的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);
}

4 个答案:

答案 0 :(得分:5)

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

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

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

  3. 写测试。

  4. 以下是示例代码。

    受测试的课程:

    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 :(得分:2)

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

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

另一个问题是您的方法是private。首先检查它的调用位置,以及是否可以通过公共方法调用它而不会有太多复杂的问题。如果没有,您可能需要(暂时)公开以启用单元测试。请注意,对私有方法进行单元测试的需要通常是一种设计气味:您的班级可能会尝试做太多,承担太多不同的责任。您可以将其某些功能提取到一个不同的类中,在该类中它变为公共类,因此可以直接进行单元测试。但首先你需要进行单元测试,以确保在重构​​时不会破坏任何东西。

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

类似技巧的推荐读数为Working Effectively with Legacy Code

答案 2 :(得分:2)

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

至于“是否有可能为此类内容创建单元测试?”,这取决于您希望单元测试的概念有多纯,您希望它们依赖于用户,以及究竟是什么{ {1}}确实。

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

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

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

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

MessageBox.Show

您的单元测试会使其成为自己的自定义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

答案 3 :(得分:1)

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

  • 验证日期
  • 通过显示消息
  • 对失败的验证作出反应

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

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

这也将使验证代码更具可重用性,因为它改变了如何将结果视为调用代码的意图。