如何为主要面向服务的应用程序编写有用的单元测试?
我已经成功使用单元测试一段时间了,但我开始认为它们只对实际执行大量逻辑的类/方法有用 - 解析器,做数学,复杂的业务逻辑 - 所有都是测试的好候选者,毫无疑问。我真的很难弄清楚如何对另一类对象进行测试:那些主要通过委托进行操作的对象。
举个例子:我当前的项目协调了很多数据库和服务。大多数类只是服务方法的集合,并且大多数方法执行一些基本的条件逻辑(可能是 for-each 循环),然后调用其他服务。
对于这样的对象,模拟实际上是唯一可行的测试策略,因此我尽职尽责地为其中的几个对象设计了模拟。我真的非常不喜欢它,原因如下:
- 使用模拟来指定行为期望,每当我更改类实现时,事情就会中断,即使它不是那种应该对单元产生影响的更改测试。在我看来,单元测试应该测试功能,而不是指定“方法需要按顺序执行 A,然后是 B,然后是 C,仅此而已”。我喜欢测试,因为我可以自由地改变事情,并相信我会知道是否有什么东西坏了——但模拟只会让改变任何事情变得很痛苦。
- 如果预期的行为很简单,那么编写模拟通常比编写类本身需要更多的工作。
- 因为我在测试中使用了完全不同的所有服务和组件对象的实现,所以最终,我所有的测试真正验证的是行为的最基本框架:“if”和“for”语句仍然有效。无聊的。我不担心这些。
我的应用程序的核心实际上是所有部分如何协同工作,所以我正在考虑 完全放弃单元测试(除了明显合适的地方)并转向外部集成测试 - 更难设置,覆盖较少可能的情况,但实际上运行系统,因为它意味着运行。
我没有看到任何使用模拟实际上有用的情况。
想法?
I've used unit tests successfully for a while, but I'm beginning to think they're only useful for classes/methods that actually perform a fair amount of logic - parsers, doing math, complex business logic - all good candidates for testing, no question. I'm really struggling to figure out how to use testing for another class of objects: those which operate mostly via delegation.
Case in point: my current project coordinates a lot of databases and services. Most classes are just collections of service methods, and most methods perform some basic conditional logic, maybe a for-each loop, and then invoke other services.
With objects like this, mocks are really the only viable strategy for testing, so I've dutifully designed mocks for several of them. And I really, really don't like it, for the following reasons:
- Using mocks to specify expectations for behavior makes things break whenever I change the class implementation, even if it's not the sort of change that ought to make a difference to a unit test. To my mind, unit tests ought to test functionality, not specify "the methods needs to do A, then B, then C, and nothing else, in that order." I like tests because I am free to change things with the confidence that I'll know if something breaks - but mocks just make it a pain in the ass to change anything.
- Writing the mocks is often more work than writing the classes themselves, if the intended behavior is simple.
- Because I'm using a completely different implementation of all the services and component objects in my test, in the end, all my tests really verify is the most basic skeleton of the behavior: that "if" and "for" statements still work. Boring. I'm not worried about those.
The core of my application is really how all the pieces work together, so I'm considering
ditching unit tests altogether (except for places where they're clearly appropriate) and moving to external integration tests instead - harder to set up, coverage of less possible cases, but actually exercise the system as it is mean to be run.
I'm not seeing any cases where using mocks is actually useful.
Thoughts?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
如果您可以编写快速且可靠的集成测试,那么我会说那就去做吧。
仅在必要时使用模拟和/或存根以保持测试的方式。
但请注意,使用模拟并不一定像您所描述的那么痛苦:
If you can write integration tests that are fast and reliable, then I would say go for it.
Use mocks and/or stubs only where necessary to keep your tests that way.
Notice, though, that using mocks is not necessarily as painful as you described:
使用模拟进行行为测试可能会导致脆弱的测试。使用存根进行基于状态的测试可以减少这个问题。Fowler 在 模拟不是存根。
,请考虑使用隔离(模拟)框架。
分支和循环是逻辑;我建议测试它们。在我看来,没有必要测试 getter 和 setter、单行纯委托方法等等。
集成测试对于像您这样的复合系统非常有价值。我会推荐它们除了单元测试之外,而不是代替它们。
您肯定会想要测试底层或组合服务的类;在那里你会看到最大的性价比。
编辑:福勒没有按照我的想法使用“经典”术语(这可能意味着我错了)。当我谈论基于状态的测试时,我的意思是向被测类注入任何依赖项的存根,对被测类进行操作,然后针对被测类进行断言。在纯粹的情况下,我不会验证存根上的任何内容。
I agree. Behavior testing with mocks can lead to brittle tests, as you've found. State-based testing with stubs reduces that issue. Fowler weighs in on this in Mocks Aren't Stubs.
For mocks or stubs, consider using an isolation (mocking) framework.
Branches and loops are logic; I would recommend testing them. There's no need to test getters and setters, one-line pure delegation methods, and so forth, in my opinion.
Integration tests can be extremely valuable for a composite system such as yours. I would recommend them in addition to unit tests, rather than instead of them.
You'll definitely want to test the classes underlying your low-level or composing services; that's where you'll see the biggest bang for the buck.
EDIT: Fowler doesn't use the "classical" term the way I think of it (which likely means I'm wrong). When I talk about state-based testing, I mean injecting stubs into the class under test for any dependencies, acting on the class under test, then asserting against the class under test. In the pure case I would not verify anything on the stubs.
编写集成测试在这里是一个可行的选择,但不应取代单元测试。但既然你说你的写作是对自己的模拟,我建议使用隔离框架(又名模拟框架),我非常确定它也适用于你的环境。
Writing Integration Tests is a viable option here, but should not replace Unit Tests. But since you stated your writing mocks yourself, I suggest using an Isolation Framework (aka Mocking Framework), which I am pretty sure of will be available for your environment too.
鉴于您在一个帖子中提出了多个问题,我将一一回答。
不要依赖“主要面向服务的应用程序”的单元测试!是的,我用一句话说了这句话。这些类型的应用程序旨在做一件事:集成服务。因此,更紧迫的是,您需要编写集成测试而不是单元测试,以确保集成正常工作。
模拟非常有用,但我不会在控制器上使用它们。控制器应该包含在集成测试中。单元测试可以涵盖服务,但如果测试量减慢了项目的速度,将它们作为单独的模块可能是明智的。
对我来说,我倾向于考虑一些事情:
在您提供的场景中,我想说您的应用程序是许多服务的集成。因此,我会严重依赖集成测试而不是单元测试。我敢打赌,您编写的大部分模拟都是针对 http 相关类等的。
我更喜欢集成/系统级测试,原因如下:
Being that you've posted several questions in one I'll answer them one by one.
Do not rely on unit tests for a "mostly service-oriented app"! Yes I said that in a sentence. These types of apps are meant to do one thing: integrate services. It's therefore more pressing that you write integration tests instead of unit tests to very that the integration is working correctly.
Mocks can be extremely useful, but I wouldn't use them on controllers. Controllers should be covered by integration tests. Services can be covered by unit tests but it may be wise to have them as separate modules if the amount of testing slows down your project.
For me, I tend to think about a few things:
In the scenario you've provided, I'd say your application is an integration of many services. Therefore, I'd lean heavily on integration tests over unit tests. I'd bet most of the Mocks you've written have been for http related classes etc.
I'm a bigger fan of integration / system level tests wherever possible for the following reasons: