Static Methods : When and when not

谁说胖子不能爱 提交于 2019-12-01 03:51:25
adrianbanks

I think you may have slightly misunderstood.

Static methods are testable. Take this method as an example:

public static int Add(int x, int y)
{
    return x + y;
}

You can test this by testing that the return value is what you expect based on the arguments passed in.

Where static methods become troublesome when testing is when you need to introduce a mock.

Let's say I have some code that calls the File.Delete() static method. To test my code without relying on the file system, I would want to replace/mock this call with a test version that just verifies that it has been called from the code being tested. This is easy to do if I had an instance of an object on which Delete() was being called. Most (all?) mocking frameworks can not mock static methods, so using a static method in my code forces me to test it differently (usually by calling the real static method).

To test something like this, I would introduce an interface:

interface IFileDeleter
{
    void Delete(string file);
}

My code would then take an instance of an object that implements this interface (either in the method call or as a parameter in the constructor), and then call its Delete() method to do the delete:

void MyMethod(string file)
{
    // do whatever...
    deleter.Delete(file);
}

To test this, I can make a mock of the IFileDeleter interface and simply verify that its Delete() method had been called. This removes the need to have a real file system as part of the test.

This may look like the code is more complex (which it is), but it pays for itself in making it far easier to test.

Avoiding statics is certainly the way to go, but when you can't or you're working with Legacy code, the following option is available. Following on from adrianbanks answer above, let's say you have the following code (apologies, its in Java as I don't know C#):

public void someMethod() {
   //do somethings
   File.delete();
   //do some more things
}

you can refactor the File.delete() into its own method like this:

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

//protected allows you to override in a subclass
protected void deleteFile() { 
   File.delete();
}

and then in preparation for your unit test create a mock class which extends the original one and stubs out that functionality:

//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; 
    }
}

and finally in your unit test:

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

Now you've executed all the functionality of someMethod() without actually deleting the file, and you still have the ability to see if that method was called.

In general, if the method:

  • Is slow
  • Is long
  • Contains complex logic
  • Uses the file system
  • Connects to a database
  • Calls a web service

then avoid making it static. (See @adrianbanks' answer for an excellent discussion of the reasons behind this and the alternatives.)

Basically, only make it static if it's a short in-memory convenience method (like many extension methods).

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!