在Stack Overflow上浏览了一些TDD标签的问题后,我得知建议针对接口而不是对象进行编程是一个好主意。
你能提供一些简单的代码示例来说明这是什么以及如何将其应用于真实的用例中吗?简单的示例对我(和其他想要学习的人)理解这些概念至关重要。
考虑以下内容:
class MyClass
{
//Implementation
public void Foo() {}
}
class SomethingYouWantToTest
{
public bool MyMethod(MyClass c)
{
//Code you want to test
c.Foo();
}
}
因为MyMethod
只接受MyClass
,如果你想用一个模拟对象来替换MyClass
以进行单元测试,那是不可能的。更好的方法是使用一个接口:
interface IMyClass
{
void Foo();
}
class MyClass : IMyClass
{
//Implementation
public void Foo() {}
}
class SomethingYouWantToTest
{
public bool MyMethod(IMyClass c)
{
//Code you want to test
c.Foo();
}
}
现在你可以测试MyMethod
,因为它只使用了一个接口,而不是具体的实现。然后,你可以实现该接口以创建任何类型的模拟或虚假物来进行测试。甚至有像Rhino Mocks的Rhino.Mocks.MockRepository.StrictMock<T>()
这样的库,它可以接受任何接口并在运行时为你构建一个模拟对象。
IocContainer container = new IocContainer();
//Register my impl for the Service Interface, with a Singleton policy
container.RegisterType(Service, ServiceImpl, LifecyclePolicy.SINGLETON);
//Use the container as a factory
Service myService = container.Resolve<Service>();
//Blissfully unaware of the implementation, call the service method.
myService.DoGoodWork();
在编写针对接口的程序时,您将编写使用接口实例而不是具体类型的代码。例如,您可能会使用以下模式,其中包含构造函数注入。虽然不需要构造函数注入和控制反转的其他部分才能针对接口进行编程,但由于您来自TDD和IoC角度,我已经将其连接起来,以便为您提供一些您希望熟悉的上下文。
public class PersonService
{
private readonly IPersonRepository repository;
public PersonService(IPersonRepository repository)
{
this.repository = repository;
}
public IList<Person> PeopleOverEighteen
{
get
{
return (from e in repository.Entities where e.Age > 18 select e).ToList();
}
}
}
传入的是接口类型的存储库对象。传递接口的好处在于能够“交换”具体实现而不更改使用方式。
例如,在运行时,IoC容器将注入与数据库连接的存储库。在测试时,您可以传递模拟或存根存储库来执行PeopleOverEighteen
方法。
这意味着要考虑通用性而非特定性。
假设你有一个应用程序,需要向用户发送一些消息进行通知。如果你使用 IMessage 等接口进行开发
interface IMessage
{
public void Send();
}
您可以按用户自定义接收消息的方式。例如,某人想通过电子邮件收到通知,因此您的IoC将创建一个EmailMessage具体类。其他人想要短信,则创建一个SMSMessage实例。
在所有这些情况下,通知用户的代码都不会改变。即使添加另一个具体类。
这个视频教程讲解了如何在C#中实践敏捷开发和测试驱动开发。
通过针对接口编码,你可以在测试中使用模拟对象而不是真实对象。通过使用一个好的模拟框架,你可以在模拟对象中做任何你想做的事情。
interface
无关。事实上,在引用此语录的书籍出版时,Java和C#甚至还不存在。 - Jörg W MittagList
的接口表示在将元素添加到列表后,该元素会出现在列表中,并且列表的长度会增加1。那么,在interface List
中实际上哪里说了呢? - Jörg W Mittag接口
有助于定义,但不足以达到大写字母 I 的接口
。这听起来怎么样? - Michael Petrotta