the following is the original text of the book 《The Art of Unit Testing, Second Edition》: Extract an interface to allow replacing underlying implementation
Listing 3.1 Extracting a class that touches the filesystem and calling it
public bool IsValidLogFileName(string fileName)
{
FileExtensionManager mgr =
new FileExtensionManager();
return mgr.IsValid(fileName);
}
class FileExtensionManager
{
public bool IsValid(string fileName)
{
//read some file here
}
}
Listing 3.2 Extracting an interface from a known class
public class FileExtensionManager : IExtensionManager
{
public bool IsValid(string fileName)
{
...
}
}
public interface IExtensionManager
{
bool IsValid (string fileName);
}
//the unit of work under test:
public bool IsValidLogFileName(string fileName)
{
IExtensionManager mgr =
new FileExtensionManager();
return mgr.IsValid(fileName);
}
Question: I don’t understand why adding an interface makes it easier to distinguish and replace class calls later? Is this question in the scope of design patterns? Should I learn some design patterns to better understand this question?
I don’t understand why adding an interface makes it easier to distinguish and replace class calls later?
That's an understandable confusion, given the limited example. The IsValidLogFileName
method is still tightly coupled to the FileExtensionManager
because it's calling its constructor.
In both the first and second example, you'd need to change one line of code if you wanted to change to a different type of extension manager. So it doesn't seem to make much of a difference.
Is this question in the scope of design patterns? Should I learn some design patterns to better understand this question?
Yes. Look into Dependency Injection. If IsValidLogFileName
used an IExtensionManager
object that was given to it, rather than creating a new FileExtensionManager()
directly, then you would totally eliminate the reference to FileExtensionManager from its code.
You could replace the FileExtensionManager
with a BlobExtensionManager
or a FakeExtensionManager
or an EmptyExtensionManager
by simply passing a different object in. Your method's code wouldn't have to change at all!
You could write unit tests that use a mock IExtensionManager
to specify what IsValid
should return for different values, to verify that IsValidLogFileName
behaves the way it should behave in a variety of circumstances.
Using an interface the way it's given in the example is just taking one step closer to decoupling yourself from implementation details. You won't see the full benefits until you can remove the implementation reference more completely from your method and its class.