何时使用接口(单元测试,IoC?)


17

我怀疑我在这里犯了一个小学生错误,正在寻求澄清。我的解决方案(C#)中有很多类-敢于说大多数-我最终为之编写了相应的接口。例如,即使我永远不可能用其他实现替换该计算器,也可以使用“ ICalculator”接口和实现该接口的“ Calculator”类。而且,这些类中的大多数与其依赖项都位于同一个项目中-实际上,它们仅需为internal,但最终成为public实现其各自接口的副作用。

我认为这种为所有内容创建接口的做法源于一些谬误:

1)我本来以为创建单元测试模拟必须有一个接口(我使用的是Moq),但是后来我发现,如果类的成员为virtual,则可以模拟该类,并且该类具有无参数的构造函数(如果我错了)。

2)我本来以为必须要有一个接口才能向IoC框架(Castle Windsor)注册一个类,例如

Container.Register(Component.For<ICalculator>().ImplementedBy<Calculator>()...

实际上,我可以针对自身注册具体类型:

Container.Register(Component.For<Calculator>().ImplementedBy<Calculator>()...

3)使用接口(例如,用于依赖项注入的构造函数参数)会导致“松散耦合”。

那我对接口发疯了吗?!我知道您通常会“使用”接口(例如公开公共API)或“可插入”功能之类的场景。我的解决方案只有少数适​​合此类用例的类,但我想知道是否所有其他接口都是不必要的,应该删除?关于上述第3点,如果这样做,我是否会违反“松散耦合”?

编辑:-我只是在玩Moq,它似乎要求方法是公共的和虚拟的,并具有公共的无参数构造函数,以便能够模拟它们。这样看来我不能拥有内部类了吗?


我很确定C#不需要您将某个类公开以实现接口,即使该接口是公开的……
vaughandroid13年

1
您不应该测试私人成员。这可以看作是神级反模式的指标。如果您需要对私有功能进行单元测试,那么通常最好将该功能封装到自己的类中。
Spoike

@Spoike所涉及的项目是一个UI项目,在其中将所有类都内部化似乎很自然,但是它们仍然需要单元测试?我在其他地方读过关于不需要测试内部方法的信息,但是我从未理解过为什么的原因。
安德鲁·史蒂芬斯

首先问问自己什么是被测单元。是整个类还是方法?该单元必须是公共的,以便正确地进行单元测试。在UI项目中,我通常将可测试的逻辑分离为均具有公共方法的控制器/视图模型/服务。实际的视图/窗口小部件已连接到控制器/视图模型/服务,并通过常规烟雾测试进行了测试(即启动应用程序并开始单击)。您可以拥有一些场景,这些场景应该测试基础功能,并且在小部件上进行单元测试会导致脆弱的测试,并且应该谨慎进行。
Spoike

@Spoike那么,您是否要公开您的VM方法,只是让我们的单元测试可以访问它们?也许那就是我要出错的地方,试图使所有内容都变得内部化。我认为我的双手可能与Moq有联系,这似乎要求公开类(而不仅仅是方法)才能模拟它们(请参阅我对Telastyn回答的评论)。
安德鲁·史蒂芬斯

Answers:


7

通常,如果您创建一个只有一个实现者的接口,那么您只会写两次东西而浪费时间。如果接口紧密地与一个实现紧密耦合,那么它们本身并不能提供松散的耦合。也就是说,如果您想在单元测试中模拟这些东西,通常这是一个很好的信号,表明您实际上将不可避免地需要多个实现器码。

如果几乎所有类都具有接口,我会认为这有点代码味道。这意味着他们几乎都以一种或另一种方式相互合作。我会怀疑这些类做得太多(因为没有辅助类),或者您抽象太多了(哦,我想要一个重力提供者接口,因为这可能会改变!)。


1
谢谢回复。如前所述,尽管我知道大多数接口永远都不会有一个以上的实现,但我还是做了一些误导性的原因。问题的一部分是我正在使用Moq框架,该框架非常适合模拟接口,但是要模拟一个类,它必须是公共的,具有公共的无参数ctr,并且要模拟的方法必须是“公共虚拟”)。
安德鲁·史蒂芬斯

我正在单元测试中的大多数类都是内部的,我不想公开它们只是为了使其可测试(尽管您可能会争论,这就是我为所有这些接口编写的内容)。如果我放弃了接口,则可能需要找到一个新的模拟框架!
安德鲁·史蒂芬斯

1
@AndrewStephens-并非世界上的所有事物都需要被嘲笑。如果这些类足够坚固(或紧密耦合)而无需接口,则它们足够坚固,可以在单元测试中按原样使用。替换它们的时间/复杂性不值得进行测试。
Telastyn

-1,因为通常您不知道在首次编写代码时需要多少个实现器。如果您从一开始就使用接口,则在编写其他实现程序时不必更改客户端。
威尔伯特

1
@wilbert-当然,一个小接口比类更具可读性,但是您没有选择一个或另一个,因为您拥有类,一个类一个接口。您可能对我的答案读得太多了。我并不是说永远不要为单个实现创建接口-实际上,大多数都应该这样做,因为大多数交互都需要这种灵活性。但是,请再次阅读该问题。为所有内容建立接口仅仅是货物培训。IoC不是原因。嘲笑不是原因。这些要求是实现这种灵活性的原因。
Telastyn

4

1)即使具体的类是可模拟的,它仍然要求您创建成员virtual并提供无参数的构造函数,这可能是令人讨厌且不必要的。您(或新的团队成员)很快就会发现自己virtual在每个新类中都系统地添加了s和无参数构造函数,而无需三思而后行,只是因为“这就是工作方式”。

当您需要模拟依赖项时,IMO接口是一个更好的主意,因为它使您可以将实现推迟到真正需要时才执行,并且可以很好地明确依赖项。

3)那是个假象?

我相信接口非常适合在协作对象之间定义消息传递协议。在某些情况下,对它们的需求值得商de域实体。但是,无论您需要与之保持稳定伙伴关系(即,注入的依赖关系而不是瞬态引用),通常都应该考虑使用接口。


3

IoC的想法是使具体类型可替换。因此,即使(现在)只有一个实现ICalculator的计算器,第二个实现也可能仅取决于接口,而不取决于Calculator的实现细节。

因此,您的IoC容器注册的第一个版本是正确的,并且您以后应该使用。

如果您将自己的课程公开或内部公开,这并没有真正的关系,也没有关系。


2

我真的更担心您似乎觉得有必要“嘲笑”整个项目中的每种类型。

测试双精度对于将您的代码与外界(第三方库,磁盘IO,网络IO等)或真正昂贵的计算隔离开来很有用。隔离框架过于宽松(您甚至真的需要它吗?您的项目是如此复杂吗?)很容易导致测试与实现耦合,并且使您的设计更加僵化。如果编写一堆测试来验证某个类依次按方法X和Y进行验证,然后将参数a,b和c传递给方法Z,那么您真的在获得价值吗?有时在测试日志记录或与第三方库的交互时会得到类似的测试,但这应该是例外,而不是规则。

一旦隔离框架告诉您必须公开内容,并且必须始终拥有无参数构造函数,就该摆脱麻烦了,因为此时您的框架正迫使您编写糟糕的代码。

更具体地讲接口,我发现它们在一些关键情况下很有用:

  • 当您需要将方法调用分派给不同的类型时,例如,当您有多种实现时(这很明显,因为大多数语言中接口的定义只是虚方法的集合)

  • 当您需要能够将其余代码与某个类隔离开来时。这是从应用程序的核心逻辑抽象文件系统和网络访问以及数据库等的常规方法。

  • 需要控制权反转以保护应用程序边界时。这是用于管理依赖项的最强大的方法之一。我们都知道高级模块和低级模块不应该彼此了解,因为它们会因不同的原因而发生变化,并且您不想依赖域模型中的HttpContext或矩阵乘法库中的某些Pdf呈现框架。使边界类实现接口(即使它们从未被替换过)也可以帮助松开层之间的耦合,并且可以大大降低依赖其他类型的类型而使依赖渗入层的风险。


1

我倾向于这样一种想法,即如果某些逻辑是私有的,那么任何人都不必担心该逻辑的实现,因此不应该对其进行测试。如果某些逻辑足够复杂以至于您想对其进行测试,则该逻辑可能会发挥不同的作用,因此应该公开。例如,如果我在私有帮助器方法中提取了一些代码,我发现它通常可以放在单独的公共类(格式化程序,解析器,映射器等)中。
至于接口,我让需要存根或模拟类的需求驱使我。像回购这样的东西,我通常希望能够存根或模拟。集成测试中的映射器(通常)不是很多。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.