我仍处于关于单元测试的学习阶段,特别是关于模拟(我正在使用 PascalMock 和DUnit 框架)。我现在偶然发现的一件事是,我找不到一种方法来将被测试的类/接口的实现细节硬编码到我的单元测试中,这感觉是错误的......
例如:我想测试一个实现了一个非常简单的界面,用于读取和写入应用程序设置(基本上是名称/值对)。呈现给消费者的界面完全不知道值的实际存储位置和方式(例如注册表、INI 文件、XML、数据库等)。当然,访问层是由另一个不同的类实现的,该类在构造时注入到测试的类中。我为此访问层创建了一个模拟对象,现在我可以完全测试接口实现类,而无需实际读取或写入任何注册表/INI 文件/任何内容。
但是,为了确保模拟在被测试类访问时的行为与真实事物完全相同,我的单元测试必须通过非常明确地定义预期的方法调用和被测试类预期的返回值来设置模拟对象。这意味着,如果我必须更改访问层的接口或测试类使用该层的方式,我还必须更改内部使用该接口的类的单元测试,即使 <我实际测试的类的界面根本没有改变。这是我在使用模拟时必须忍受的事情,还是有更好的方法来设计类依赖关系来避免这种情况?
I'm still in the learning stages regarding unit-testing and in particular regarding mocking (I'm using the PascalMock and DUnit frameworks). One thing I now stumbled over was that I couldn't find a way around hard-coding implementation details of the tested class/interface into my unit test and that just feels wrong...
For example: I want to test a class that implements a very simple interface for reading and writing application settings (basically name/value pairs). The interface that is presented to the consumer is completely agnostic to where and how the values are actually stored (e.g. registry, INI-file, XML, database, etc.). Naturally, the access layer is implemented by yet a different class that gets injected into the tested class on construction. I created a mock object for this access layer and I am now able to fully test the interface-implementing class without actually reading or writing anything to any registry/INI-file/whatever.
However, in order to ensure the mock behaves exactly like the real thing when accessed by the tested class, my unit tests have to set up the mock object by very explicitly defining expected method calls and the return values expected by the tested class. This means that if I should ever have to make changes to the interface of the access layer or to the way that the tested class uses that layer I will also have to change the unit tests for the class that internally uses that interface even though the interface of the class I'm actually testing hasn't changed at all. Is this something I will just have to live with when using mocks or is there a better way to design the class-dependencies that would avoid this?
发布评论
评论(3)
正确的。
正确。
“实际测试”?你的意思是暴露的接口类?没关系。
“测试”(接口)类使用访问层的方式意味着您已将内部接口更改为访问层。接口更改(甚至是内部更改)需要进行测试更改,如果您做错了什么,可能会导致损坏。
这没有什么问题。事实上,重点是对访问层的任何更改必须需要对模拟进行更改,以确保更改“有效”。
测试不应该是“稳健的”。应该是脆的。如果您做出的更改会改变内部行为,那么事情可能会崩溃。如果你的测试太稳健,他们就不会测试任何东西——它们只会工作。那是错误的。
测试应该只在正确的原因下有效。
Correct.
Correct.
"Actually testing"? You mean the exposed interface class? That's fine.
The way the "tested" (interface) class uses the access layer means you've changed the internal interface to the access layer. Interface changes (even internal ones) require test changes and may lead to breakage if you've done something wrong.
Nothing wrong with this. Indeed, the whole point is that any change to the access layer must require changes to the mocks to assure that the change "works".
Testing is not supposed to be "robust". It's supposed to be brittle. If you make a change that alters internal behavior, then things can break. If your tests were too robust they wouldn't test anything -- they'd just work. And that's wrong.
Tests should only work for the exact right reason.
很多时候,模拟(尤其是像 JMock 这样的敏感框架)会迫使您考虑与您尝试测试的行为不直接相关的细节,有时这甚至可以通过暴露做得太多的可疑代码来提供帮助。并且有太多的调用/依赖。
但是,就您的情况而言,如果我正确阅读您的描述,听起来您确实没有问题。如果您正确设计了读/写层并具有适当的抽象级别,则不必更改它。
编写抽象访问层的目的不是为了避免这种情况吗?一般来说,遵循开放/封闭原则,这种接口<em >不应该改变,也不应该破坏与使用它的类的合同,并且通过扩展,它也不会破坏你的单元测试。现在,如果您更改方法调用的顺序,或者必须对抽象层进行新的调用,那么,是的,特别是对于某些框架,您的模拟期望将会被打破。这只是使用模拟成本的一部分,而且是完全可以接受的。但一般来说,界面本身应该保持稳定。
A lot of times mocks (particularly sensitive frameworks like JMock) force you to account for details that don't relate directly to the behavior you're trying to test, and sometimes this can even be helpful by exposing suspect code that is doing too much and has too many calls/dependencies.
However in your case, if I read your description right, it sounds like you really don't have a problem. If you design the read/write layer correctly and with an appropriate level of abstraction, you shouldn't have to change it.
Isn't the point of writing the abstracted access layer to avoid this? In general, following the Open/Closed principle, an interface of this sort shouldn't change and shouldn't break the contract with the class that consumes it, and by extension it won't break your unit tests either. Now if you change the order of the method calls, or have to make new calls to the abstracted layer, then, yes, particularly with some frameworks, your mock expectations will break. This is just part of the cost of using mocks, and it's perfectly acceptable. But the interface itself should, in general, remain stable.
只是为了给您的示例添加一些名称,
您当前有兴趣测试RegistryBasedDictionary。单元测试将为RegistryAccessor Role注入模拟依赖项,并测试与依赖项的预期交互。
更改RegistryBasedDictionary 测试的唯一原因是更改RegistryBasedDictionary 的行为,而不是更改其任何依赖项。因此,如果它与其依赖项的交互或角色/合同发生变化,则需要更新测试。这是为了获得独立测试的灵活性而需要付出的基于交互的测试的代价。但实际上,这种情况并不经常发生。
Just to put some names to your example,
You are currently interested in testing RegistryBasedDictionary. The unit tests would inject a mock dependency for the RegistryAccessor Role and would test the expected interaction with the dependencies.
The only reason to change RegistryBasedDictionary tests, would be a change in the behavior of RegistryBasedDictionary and not in any of its dependencies. So if its interaction with its dependencies or the roles/contracts change, the tests would need to be updated. That is the price of interaction-based tests you need to pay, for the flexibility to test in isolation. However in practice, it doesn't that happen that often.