3

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

用户可以选择一个 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

4 回答 4

5

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

  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())
    }
}

测试引入的设计更改为您提供了很好的系统解耦。当未编写外部依赖项时,可以为特定类编写测试。

于 2011-02-15T08:40:25.757 回答
2

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

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

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

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

类似技巧和更多的推荐阅读是有效地使用旧代码

于 2011-02-15T08:38:10.063 回答
2

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

至于“是否可以为这样的东西创建单元测试?”,这取决于您希望对单元测试概念的纯洁程度,您希望它们对用户的依赖程度,以及具体做什么//show a custom message

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

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

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

如果您不想仅仅为了测试这个类的逻辑而进行 UI 自动化测试,那么我所知道的最好的方法是修改您的类以使用依赖注入。将所有实际输出代码 ( 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涉及计算。ToDate

于 2011-02-15T08:45:15.590 回答
1

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

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

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

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

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

于 2011-02-15T08:46:37.340 回答