我已经开始了漫长而艰巨的学习和追求 应用TDD到我的工作流程。我觉得TDD非常符合IoC原则。
在SO中浏览了一些TDD标签的问题之后,我读到对接口(而不是对象)进行编程是一个好主意。
您能否提供简单的代码示例,以及如何在实际用例中应用它?简单的示例对我(以及其他想学习的人)来说是理解概念的关键。
我已经开始了漫长而艰巨的学习和追求 应用TDD到我的工作流程。我觉得TDD非常符合IoC原则。
在SO中浏览了一些TDD标签的问题之后,我读到对接口(而不是对象)进行编程是一个好主意。
您能否提供简单的代码示例,以及如何在实际用例中应用它?简单的示例对我(以及其他想学习的人)来说是理解概念的关键。
interface
Java或C#无关。实际上,当编写引用该书的书时,甚至Java和C#都不存在。
List
说,后add
荷兰国际集团的元素列表,所述元素是在列表中,由清单的长度增加1
。它在[ interface List
](Download.Oracle.Com/javase/7/docs/api/java/util/List.html#add)中实际在哪里说呢?
Answers:
考虑:
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>()
,它们都可以采用任何接口,并为您动态构建一个模拟对象。
interface
在整个代码中使用关键字并不意味着它是针对Interface进行编程的。思想实验:采用可怕的紧密耦合的内聚代码。对于每个类,只需将其复制并粘贴,删除所有方法主体,将替换为class
关键字interface
并将代码中的所有引用更新为该类型。现在的代码更好了吗?
这全都是亲密关系。如果您对实现(已实现的对象)进行编码,那么您将与该“其他”代码有密切的关系。这意味着您必须知道如何构造它(即,它具有什么依赖关系,可能作为构造函数参数,可能作为setter),何时处置它,没有它,您可能无法做很多事情。
实现的对象前面的接口使您可以做一些事情-
**更新**有人要求提供IOC容器(工厂)的示例。几乎所有平台都有很多,但是它们的核心工作如下:
您可以在应用程序启动例程中初始化容器。一些框架通过配置文件或代码或两者来实现。
您“注册”您希望容器为它们创建的实现作为工厂为其实现的接口(例如:为服务接口注册MyServiceImpl)。在此注册过程中,通常可以提供一些行为策略,例如,每次创建一个新实例还是使用一个(吨)实例
当容器为您创建对象时,它会在创建过程中将所有依赖项注入到这些对象中(即,如果您的对象依赖于另一个接口,则依次提供该接口的实现,依此类推)。
伪编码地看起来像这样:
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具体类。其他人想要SMS,并且您创建了SMSMessage的实例。
在所有这些情况下,用于通知用户的代码将永远不会更改。即使您添加另一个具体的类。
在执行单元测试时针对接口进行编程的最大优点是,它使您可以将一段代码与要单独测试或在测试期间进行模拟的任何依赖项隔离开。
我之前在这里提到的一个示例是使用接口访问配置值。您可以提供一个或多个接口来访问配置值,而不是直接查看ConfigurationManager。通常,您会提供一种从配置文件中读取的实现,但是对于测试,您可以使用仅返回测试值或引发异常或其他任何行为的实现。
还请考虑您的数据访问层。将您的业务逻辑紧密耦合到特定的数据访问实现上,很难在没有整个数据库方便地处理所需数据的情况下进行测试。如果您的数据访问隐藏在接口后面,则可以仅提供测试所需的数据。
使用界面会增加可用于测试的“表面积”,从而可以进行更细粒度的测试,这些测试确实可以测试代码的各个单元。
阅读文档后,像测试使用它的人一样测试您的代码。请勿根据已掌握的知识来进行任何测试,因为您已经编写或阅读了代码。您要确保代码行为正常符合预期。
在最好的情况下,您应该能够使用测试作为示例,Python中的doctests就是一个很好的例子。
如果您遵循这些准则,则更改实现不成问题。
同样,根据我的经验,最好是测试应用程序的每个“层”。您将拥有原子单位,而原子单位本身没有依赖关系,并且您将拥有依赖于其他单位的单位,直到最终到达本身就是单位的应用程序为止。
您应该测试每一层,不要依赖于这样的事实,即通过测试单元A还要测试单元A所依赖的单元B(该规则也适用于继承。)这也应该被视为实现细节,甚至尽管您可能会觉得自己在重复自己。
请记住,一旦书面测试几乎不可能改变,而他们测试的代码几乎肯定会改变。
实际上,还有IO和外界的问题,因此您需要使用接口,以便在必要时可以创建模拟。
在更动态的语言中,这并不是什么大问题,在这里您可以使用鸭子类型,多重继承和混合来组成测试用例。如果您一般开始不喜欢继承,那么您可能做对了。