静态方法:何时何时不

MSI*_*MSI 8 .net tdd domain-driven-design

我是TDD和DDD的新手,我有一个关于静态方法的简单问题.TDD的大多数专家用一句话说静态方法是坏的(我们应该忘记创建大量的静态实用程序,我们(或我)以前用过它们是不可测试的.我可以看出为什么它们是不可测试(这里可以找到一个很棒的澄清文章,对于那些感兴趣的人,但我想我是这里唯一的菜鸟:()但是我想知道从TDD角度来看使用静态有一个很好的干净指南吗?

对于大多数人来说,这可能是一个非常愚蠢的问题,但是一些提示会很棒,我只想知道这里的专家如何看待静态的东西.提前致谢.

编辑:在寻找答案的过程中,我找到了2个关于静态使用的其他好线程(虽然不是TDD关注),我觉得对于那些感兴趣的人(我自己也包括在内)很好.

adr*_*nks 11

我想你可能会有些误解.

静态方法是可测试的.以此方法为例:

public static int Add(int x, int y)
{
    return x + y;
}
Run Code Online (Sandbox Code Playgroud)

您可以通过根据传入的参数测试返回值是否符合预期来测试此值.

当测试是需要引入模拟时,静态方法变得麻烦.

假设我有一些调用File.Delete()静态方法的代码.为了在不依赖文件系统的情况下测试我的代码,我想用一个测试版本替换/模拟这个调用,该测试版本只是验证它是从被测试的代码调用的.如果我有一个Delete()正在调用的对象的实例,这很容易做到.大多数(所有?)模拟框架都不能模拟静态方法,因此在我的代码中使用静态方法迫使我以不同方式测试它(通常通过调用真正的静态方法).

为了测试这样的东西,我会介绍一个接口:

interface IFileDeleter
{
    void Delete(string file);
}
Run Code Online (Sandbox Code Playgroud)

然后我的代码将获取一个实现此接口的对象的实例(在方法调用中或作为构造函数中的参数),然后调用其Delete()方法执行删除:

void MyMethod(string file)
{
    // do whatever...
    deleter.Delete(file);
}
Run Code Online (Sandbox Code Playgroud)

为了测试这个,我可以模拟IFileDeleter界面并简单地验证它的Delete()方法已被调用.这消除了将实际文件系统作为测试的一部分的需要.

这可能看起来像代码更复杂(它是),但它使自己付出代价更容易测试.


Chr*_*ght 8

避免静态当然是要走的路,但是当你不能或者你正在使用Legacy代码时,可以使用以下选项.继上面的adrianbanks回答之后,假设您有以下代码(道歉,它在Java中,因为我不知道C#):

public void someMethod() {
   //do somethings
   File.delete();
   //do some more things
}
Run Code Online (Sandbox Code Playgroud)

您可以将File.delete()重构为自己的方法,如下所示:

public void someMethod() {
   //do somethings
   deleteFile();
   //do some more things
}

//protected allows you to override in a subclass
protected void deleteFile() { 
   File.delete();
}
Run Code Online (Sandbox Code Playgroud)

然后在准备你的单元测试时创建一个mock类,它扩展了原来的一个并且存根该功能:

//Keep all the original functionality, but stub out the file delete functionality to 
//prevent it from using the real thing and while you're at it, keep a record that the
//method was called.
public class MockClass extends TheRealClass {
    boolean fileDeleteCalled = false;

    @Override
    protected void deleteFile()
        //don't actually delete the file, 
        //just record that the method to do so was called
        fileDeleteCalled = true;
    }

    public boolean fileDeleteCalled() { 
        return fileDeleteCalled; 
    }
}
Run Code Online (Sandbox Code Playgroud)

最后在你的单元测试中:

//This would normally be instantiated in the @Before method
private MockClass unitUnderTest = new MockClass();

@Test
public void testFileGetsDeleted(){
    assertFalse(unitUnderTest.fileDeleteCalled());
    unitUnderTest.someMethod();
    assertTrue(unitUnderTest.fileDeleteCalled());
}
Run Code Online (Sandbox Code Playgroud)

现在你已经执行了someMethod()的所有功能而没有实际删除文件,你仍然能够看到是否调用了该方法.


Tru*_*ill 7

一般来说,如果方法:

  • 是慢的
  • 包含复杂的逻辑
  • 使用文件系统
  • 连接到数据库
  • 调用Web服务

然后避免让它静止.(请参阅@adrianbanks的答案,以便对此背后的原因和替代方案进行精彩讨论.)

基本上,如果它是一个简短的内存中方便方法(如许多扩展方法),只能使它静态.