【问题标题】:Should I add features in a class just to make it testable?我应该在一个类中添加特性只是为了使其可测试吗?
【发布时间】:2012-06-19 20:18:08
【问题描述】:

我仍在尝试掌握单元测试的窍门,我有一个简单的问题。今天我想为一个非常简单的函数写一个测试。这个函数就是这样做的:

void OnSomething()
{
   increment++;

   if (increment == 20)
     SaveIt();
}

我说,这个功能是可以测试的。我可以编写一个调用它 20 次的测试,然后验证 SaveIt 是否已被调用。

然后我的疑问出现了。如何测试 SaveIt 是否已被调用?我的第一个答案是添加一个布尔值,但后来我想:添加类功能只是为了使其可测试是否正确?

请指教。谢谢。

【问题讨论】:

    标签: unit-testing testing automated-tests


    【解决方案1】:

    我建议让 SaveIt 返回一个成功或失败的结果,这只是让整体测试更容易。你可以做一些简单的事情,比如让它返回一个布尔值,或者如果你需要报告它是通过还是失败,你也可以创建一个通用的结果类,它还包含设置消息的能力。

    一个简单的例子

    public class Result
    {
       public bool IsSuccess;
       public List<string> Messages;
    }
    

    在单元测试中,您尝试仅测试 OnSomething 行为——不应测试“SaveIt”内部发生的情况。因此,理想情况下,您希望 SaveIt() 出现在另一个类中,这样您就可以模拟它的响应。

    我为此使用最小起订量。起订量免费,您可以在这里获取:http://code.google.com/p/moq/

    我的方法会变成

    Result OnSomething()
    {
        Result result=null;
        increment++;
        if(increment == 20)
        {
             result = saver.SaveIt();
        }
        return result;
    }
    

    您的类构造函数将采用实现 ISaver 接口的对象(定义 SaveIt() 方法)(理想情况下由 DI 框架注入,但如果需要,您可以手动生成它)。

    现在在您的单元测试中,您将创建一个模拟版本的 ISaver,并告诉它在被调用时返回什么:

     Mock<ISaver> mock = new Mock<ISaver>();
     mock.Setup(x=> x.SaveIt()).Returns(new Result{IsSuccess=true});
    

    您将在构造函数 ISaver 参数中通过 mock.Object 实例化您的类。

    例如

     MyClass myClass = new MyClass(mock.Object);  
     //(assuming it didn't have other parameters)
    

    然后,您可以断言 result 是否为 null ——如果它从未被调用,它将为 null,因为您在上面所做的设置永远不会触发。

     (in nunit)
     Result result = myClass.OnSomething();
     Assert.IsNotNull(result);
    

    如果您真的不希望 OnSomething() 返回结果,或者因为它是一个事件而不能返回结果,那么我会让 OnSomething() 调用一个方法来为您完成工作:

     void OnSomething()
     {
          Result result = DoTheWork();
     }
    
     Result DoTheWork()
     {
        Result result=null;
        increment++;
        if(increment == 20)
        {
             result = saver.SaveIt();
        }
        return result;
     }
    

    然后在 DoTheWork() 而不是 OnSomething() 上运行您的单元测试。

    【讨论】:

    • 是的,这正是我的想法,感谢澄清我的疑虑。现在我必须向我的同事解释这是应该如何做的:)
    【解决方案2】:

    绝对不是!生产代码根本不应该依赖于测试,但测试应该验证实际代码的正确行为。这可以通过几种方法来实现,例如IOC,以及使用mocks。你可以看看一些现有的框架,它们可以大大简化你的生活:

    【讨论】:

    • 为了使用模拟,我必须先使用接口 :) 这是心态、习惯和重构的问题
    • 不完全是,随着当前框架的进步,模拟不一定需要接口来从它们创建存根。这就是运行时模拟而不是硬编码模拟的美妙之处。看看我建议的框架,你就会明白我的意思。
    • 对不起,我在这方面的专业领域仅限于 java,所以你可能想尝试谷歌搜索 ;)
    猜你喜欢
    • 2012-09-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-25
    • 2020-12-11
    • 2018-02-12
    • 2011-06-05
    相关资源
    最近更新 更多