C# 如何对缓存之类的实现细节进行单元测试

C# 如何对缓存之类的实现细节进行单元测试,c#,tdd,nunit,rhino-mocks,C#,Tdd,Nunit,Rhino Mocks,因此,我有一个类,其方法如下: public class SomeClass { ... private SomeDependency m_dependency; public int DoStuff() { int result = 0; ... int someValue = m_dependency.GrabValue(); ... return result; }

因此,我有一个类,其方法如下:

public class SomeClass
{
    ...

    private SomeDependency m_dependency;

    public int DoStuff()
    {
        int result = 0;
        ...
        int someValue = m_dependency.GrabValue();
        ...
        return result;
    }  
}
我决定不再每次调用
m_dependency.GrabValue()
,而是将值缓存在内存中(即在这个类中),因为我们每次都会得到相同的值(依赖关系消失,并从几乎从不更改的表中获取一些数据)

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

[测试]
public void CacheThatValue()
{
var depend=MockRepository.GeneraMock();
depend.Expect(d=>d.GrabValue()).Repeat.Once().Return(1);
var sut=新的SomeCLass(依赖);
int result=sut.DoStuff();
结果=sut.DoStuff();
depend.verifyallexpections();
}

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

我认为缓存是正交的。我会找到一种将缓存逻辑拉到方法之外的方法,可以通过更改SomeDependency或以某种方式包装它(现在我有了一个基于lambda表达式的缓存类的好主意——yum)

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

单元测试不应该测试实现,而应该测试行为。同时,受试者应该有一套狭义的行为

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

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

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

这似乎是“测试驱动设计”的一个案例。如果缓存是子依赖项的一个实现细节,因此无法直接测试,那么可能需要公开它的一些功能(具体地说,它的缓存行为),并且由于在子依赖项中公开它不是自然的,因此需要在另一个类中公开(我们称之为“缓存”)。当然,在缓存中,行为是契约公开的,因此是可测试的


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

很抱歉问这个问题,但是为什么要测试一些实现细节呢?好的一点:“单元测试不应该测试实现,它们应该测试行为。同时,被测试的对象应该有一组狭义的行为。”这回答了我的问题(正如Robert对我的问题的评论)
depend.Expect(d => d.GrabValue()).Repeat.Once().Return(1);
depend.Expect(d => d.GrabValue()).Repeat.Never();