以下是《The Art of Unit Testing,Second Edition》一书的原文:提取接口以允许替换底层实现
- 在这种技术中,您需要将涉及文件系统的代码分解到一个单独的类中。这样你就可以很容易地区分它,并在以后从测试中替换对该类的调用(如图3.3所示)。第一个清单显示了需要更改代码的地方。
- 清单3.1提取与文件系统相关的类并调用它 *
public bool IsValidLogFileName(string fileName)
{
FileExtensionManager mgr =
new FileExtensionManager();
return mgr.IsValid(fileName);
}
class FileExtensionManager
{
public bool IsValid(string fileName)
{
//read some file here
}
}
- 接下来,您可以告诉您的测试类,它将处理某种形式的ExtensionManager,而不使用具体的FileExtensionManager类,而不知道它的具体实现。在.NET中,这可以通过使用基类或FileExtensionManager将扩展的接口来实现。下一个清单显示了在设计中使用新接口以使其更具可测试性。图3.4显示了这种实现的示意图。
- 清单3.2从已知类提取接口 *
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);
}
- 您创建了一个带有IsValid(string)方法的接口,并使FileExtensionManager实现该接口。它仍然以完全相同的方式工作,只是现在您可以用您自己的“假”管理器替换“真实的”管理器,稍后您将创建“假”管理器来支持您的测试。
**问题:**我不明白为什么增加接口会让以后区分和替换类调用变得更容易?这个问题属于设计模式的范畴吗?我应该学习一些设计模式来更好地理解这个问题吗?
2条答案
按热度按时间m2xkgtsf1#
我不明白为什么添加一个接口会使以后区分和替换类调用变得更容易?
这是一个可以理解的混淆,因为例子有限。
IsValidLogFileName
方法仍然与FileExtensionManager
紧密耦合,因为它调用了自己的构造函数。在第一个和第二个示例中,如果您想更改为不同类型的扩展管理器,则需要更改一行代码。所以看起来没什么区别。
这个问题属于设计模式的范畴吗?我应该学习一些设计模式来更好地理解这个问题吗?
是的。查一下 * 依赖性注射 *。如果
IsValidLogFileName
使用给定的IExtensionManager
对象,而不是直接创建new FileExtensionManager()
,那么您将从其代码中完全消除对FileExtensionManager的引用。您可以通过简单地传入不同的对象,将
FileExtensionManager
替换为BlobExtensionManager
或FakeExtensionManager
或EmptyExtensionManager
。你的方法的代码根本不需要改变!您可以编写单元测试,使用 mock
IExtensionManager
来指定IsValid
在不同值下应返回什么,以验证IsValidLogFileName
在各种情况下的行为方式。以示例中给出的方式使用接口,只是离实现细节解耦更近了 * 一步 *。在您能够更完全地从方法及其类中删除实现引用之前,您不会看到全部好处。
n3h0vuf22#
从接口继承的原因之一是,当你在API中公开你的方法时,实现逻辑不会被公开,而只有方法定义会被API用户看到。