在使用模拟对象时,如何防止单元测试需要有关实现内部的知识?

Oli*_*sen 8 oop unit-testing mocking dunit pascalmock

我还处于关于单元测试的学习阶段,特别是关于模拟(我正在使用PascalMockDUnit框架).我现在偶然发现的一件事是,我找不到将测试类/接口的硬编码实现细节硬编码到我的单元测试中的方法,这只是感觉不对...

例如:我想测试一个实现非常简单的接口的类,用于读取和编写应用程序设置(基本上是名称/值对).呈现给消费者的界面完全不知道实际存储值的位置和方式(例如,注册表,INI文件,XML,数据库等).当然,访问层是由一个不同的类实现的,该类在构造时被注入到测试类中.我为这个访问层创建了一个模拟对象,现在我可以完全测试接口实现类,而无需实际读取或写入任何注册表/ INI文件/任何内容.

但是,为了确保模拟行为与被测试类访问时的真实行为完全相同,我的单元测试必须通过非常明确地定义预期的方法调用和测试类所期望的返回值来设置模拟对象.这意味着如果我必须更改访问层的接口或测试类使用该层的方式,我还必须更改内部使用该接口的类的单元测试,即使接口我实际上测试的类没有改变.这是我在使用模拟时必须要使用的东西,还是有更好的方法来设计避免这种情况的类依赖性?

S.L*_*ott 7

为了确保模拟的行为与被测试类访问时的真实内容完全相同,我的单元测试必须通过非常明确地定义预期的方法调用和测试类所期望的返回值来设置模拟对象.

正确.

更改访问层的接口或测试类使用该层的方式我还必须更改单元测试

正确.

即使我正在测试的类的接口根本没有改变.

"实际测试"?你的意思是暴露的接口类?没关系.

"测试"(接口)类使用访问层的方式意味着您已将内部接口更改为访问层.界面更改(即使是内部更改)需要更改测试,如果您做错了可能会导致破损.

这没什么不对.实际上,重点是对访问层的任何更改都必须要求对模拟进行更改,以确保更改"有效".

测试不应该是"强大的".它应该是脆弱的.如果你做出改变内部行为的改变,那么事情就会破裂.如果你的测试过于强大,他们就不会测试任何东西 - 他们只是工作.那是错的.

测试应该只是出于正确的原因.

  • 我鼓励OP在此处查看有关Classicist vs. Mockist测试驱动开发的更多信息<http://codebetter.com/blogs/ian_cooper/archive/2008/02/04/classicist-vs-mockist-test-driven-development .aspx>,特别是<http://stackoverflow.com/questions/184666/should-i-practice-mockist-or-classical-tdd>.与你所说的并不矛盾,但它提供了一些其他的观点. (3认同)
  • 谢谢,这听起来像一个非常好的推理线.然而,我一直认为单元测试应该将他们的主题(即测试的类和方法)更像是黑盒子,完全集中在*他们做什么而不是他们如何做*.然而在这种情况下,我基本上被迫以模拟期望的形式复制几乎所有测试方法的实现......这是不是与黑盒原则完全相反?或者我完全错了? (2认同)

Dav*_*ims 6

这是我在使用模拟时必须要使用的东西,还是有更好的方法来设计避免这种情况的类依赖性?

很多时候,模拟(特别是像JMock这样的敏感框架)会强迫您考虑与您尝试测试的行为无直接关系的细节,有时甚至可以通过暴露过多的可疑代码来帮助您并且有太多的调用/依赖.

但是在你的情况下,如果我正确阅读你的描述,听起来你真的没有问题.如果正确设计读/写层并具有适当的抽象级别,则不必更改它.

这意味着如果我必须更改访问层的接口或测试类使用该层的方式,我还必须更改内部使用该接口的类的单元测试,即使接口我实际上测试的类没有改变.

编写抽象访问层是不是要避免这种情况?一般来说,遵循开放/封闭原则,这种类型的接口不应该改变,不应该与使用它的类断开合同,并且通过扩展它也不会破坏你的单元测试.现在,如果你改变了方法调用的顺序,或者必须对抽象层进行新的调用,那么,是的,特别是对于某些框架,你的模拟期望会破坏.这只是使用模拟成本的一部分,而且完全可以接受.但是接口本身通常应该保持稳定.