【问题标题】:How do I unit test an implementation detail like caching如何对缓存等实现细节进行单元测试
【发布时间】:2009-07-02 03:30:50
【问题描述】:

所以我有一个类的方法如下:

public class SomeClass
{
    ...

    private SomeDependency m_dependency;

    public int DoStuff()
    {
        int result = 0;
        ...
        int someValue = m_dependency.GrabValue();
        ...
        return result;
    }  
}

我已经决定,与其每次都调用m_dependency.GrabValue(),我真的想将值缓存在内存中(即在这个类中),因为无论如何我们每次都会得到相同的值(依赖开始并从几乎不会更改的表中获取一些数据)。

我在尝试在单元测试中描述这种新行为时遇到了问题。我尝试了以下方法(我将 NUnit 与 RhinoMocks 一起使用):

[Test]
public void CacheThatValue()
{
    var depend = MockRepository.GeneraMock<SomeDependency>();

    depend.Expect(d => d.GrabValue()).Repeat.Once().Return(1);

    var sut = new SomeCLass(depend);
    int result = sut.DoStuff();
    result = sut.DoStuff();
    depend.VerifyAllExpectations();
}

但这不起作用;即使没有对功能进行任何更改,该测试也通过了。我做错了什么?

【问题讨论】:

  • 很抱歉提出这个问题,但为什么要测试一些实现细节?

标签: c# tdd nunit rhino-mocks


【解决方案1】:

我认为缓存与 Do(ing)Stuff 是正交的。我会找到一种方法将缓存逻辑拉到方法之外,或者通过更改 SomeDependency 或以某种方式包装它(我现在对基于 lambda 表达式的缓存类有了一个很酷的想法——yum)。

这样您的 DoStuff 测试不需要更改,您只需要确保它们与新包装器一起使用即可。然后,您可以独立地测试 SomeDependency 或其包装器的缓存功能。使用架构良好的代码,放置缓存层应该相当容易,您的依赖项和实现都不应该知道其中的区别。

单元测试不应该测试实现,它们应该测试行为。同时,被测对象应该有一组狭义的行为。

要回答您的问题,您使用的是动态模拟,默认行为是允许任何未配置的调用。额外的调用只是返回“0”。您需要设置一个期望,即不再对依赖项进行调用:

depend.Expect(d => d.GrabValue()).Repeat.Once().Return(1);
depend.Expect(d => d.GrabValue()).Repeat.Never();

您可能需要进入录制/回放模式才能使其正常工作。

【讨论】:

  • 好点:“单元测试不应该测试实现,它们应该测试行为。同时,被测试的主题应该有一组狭义的行为。”这回答了我的问题(罗伯特对我的问题的评论也是如此)
【解决方案2】:

这似乎是“测试驱动设计”的案例。如果缓存是 SubDependency 的实现细节 - 因此不能直接测试 - 那么可能需要公开它的一些功能(特别是它的缓存行为) - 因为在 SubDependency 中公开它是不自然的,需要暴露在另一个类中(我们称之为“缓存”)。当然,在 Cache 中,行为是契约性的——公开的,因此是可测试的。

所以测试 - 和气味 - 告诉我们我们需要一个新课程。测试驱动设计。不是很好吗?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-02-05
    • 1970-01-01
    • 2015-11-03
    • 2011-11-28
    • 2014-09-06
    • 2014-12-09
    • 2014-11-13
    • 1970-01-01
    相关资源
    最近更新 更多