Static Methods : When and when not
I am new to TDD and DDD and I have one simple question regard开发者_C百科ing static methods in general. Most of the gurus of TDD says in one word that static methods are bad (and that we should forget about creating tons of static utilities that we (um or I) used to make before as they are not testable. I can see why they are not testable ( a great clarification article can be found here for those who are interested but I guess I am the only noob here :( ) but I was wondering is there a nice and clean guideline for using statics from TDD point of view?
This may be really silly question for most of you but some tips would be great and I just want to know how experts here think of static stuff. Thanks in advance.
Edit: While looking for answer I found 2 other nice threads regarding usage of static ( not TDD concern though) which I guess are good reads for those who are interested(myself inclusive).
- Topic 1
- Topic 2
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.
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).
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.
精彩评论