如何为主要面向服务的应用程序编写有用的单元测试?

发布于 2024-08-07 06:18:32 字数 747 浏览 6 评论 0原文

我已经成功使用单元测试一段时间了,但我开始认为它们只对实际执行大量逻辑的类/方法有用 - 解析器,做数学,复杂的业务逻辑 - 所有都是测试的好候选者,毫无疑问。我真的很难弄清楚如何对另一类对象进行测试:那些主要通过委托进行操作的对象。

举个例子:我当前的项目协调了很多数据库和服务。大多数类只是服务方法的集合,并且大多数方法执行一些基本的条件逻辑(可能是 for-each 循环),然后调用其他服务。

对于这样的对象,模拟实际上是唯一可行的测试策略,因此我尽职尽责地为其中的几个对象设计了模拟。我真的非常不喜欢它,原因如下:

  1. 使用模拟来指定行为期望,每当我更改类实现时,事情就会中断,即使它不是那种应该对单元产生影响的更改测试。在我看来,单元测试应该测试功能,而不是指定“方法需要按顺序执行 A,然后是 B,然后是 C,仅此而已”。我喜欢测试,因为我可以自由地改变事情,并相信我会知道是否有什么东西坏了——但模拟只会让改变任何事情变得很痛苦。
  2. 如果预期的行为很简单,那么编写模拟通常比编写类本身需要更多的工作。
  3. 因为我在测试中使用了完全不同的所有服务和组件对象的实现,所以最终,我所有的测试真正验证的是行为的最基本框架:“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:

  1. 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.
  2. Writing the mocks is often more work than writing the classes themselves, if the intended behavior is simple.
  3. 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

得不到的就毁灭 2024-08-14 06:18:32

如果您可以编写快速且可靠的集成测试,那么我会说那就去做吧。
仅在必要时使用模拟和/或存根以保持测试的方式。

但请注意,使用模拟并不一定像您所描述的那么痛苦:

  1. 模拟 API 允许您使用松散/非严格模拟,这将允许从被测单元对其协作者进行所有调用。因此,您不需要记录所有调用,而只需记录那些需要生成测试所需结果的调用,例如方法调用的特定返回值。
  2. 有了一个好的模拟 API,您将需要编写很少的测试代码来指定模拟。在某些情况下,您可能会使用单个字段声明或应用于测试类的单个注释。
  3. 您可以使用部分模拟,以便对于给定的测试仅实际模拟服务/组件类的必要方法。这可以在不指定字符串中的方法的情况下完成。

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:

  1. Mocking APIs let you use loose/non-strict mocks, which will allow all invocations from the unit under test to its collaborators. Therefore, you don't need to record all invocations, but only those which need to produce some required result for the test, such as a specific return value from a method call.
  2. With a good mocking API, you will have to write little test code to specify mocking. In some cases you may get away with a single field declaration, or a single annotation applied to the test class.
  3. You can use partial mocking so that only the necessary methods of a service/component class are actually mocked for a given test. And this can be done without specifying said methods in strings.
十二 2024-08-14 06:18:32

在我看来,单元测试应该测试
功能,不指定“
方法需要先做A,然后做B,然后做C,
没有别的,按这个顺序。”

使用模拟进行行为测试可能会导致脆弱的测试。使用存根进行基于状态的测试可以减少这个问题。Fowler 在 模拟不是存根

编写模拟通常需要更多工作
而不是自己编写类

,请考虑使用隔离(模拟)框架。

最后,我所有的测试都确实验证了
是最基本的骨架
行为:表示“如果”和“对于”
声明仍然有效

分支和循环是逻辑;我建议测试它们。在我看来,没有必要测试 getter 和 setter、单行纯委托方法等等。

集成测试对于像您这样的复合系统非常有价值。我会推荐它们除了单元测试之外,而不是代替它们。

您肯定会想要测试底层或组合服务的类;在那里你会看到最大的性价比。

编辑:福勒没有按照我的想法使用“经典”术语(这可能意味着我错了)。当我谈论基于状态的测试时,我的意思是向被测类注入任何依赖项的存根,对被测类进行操作,然后针对被测类进行断言。在纯粹的情况下,我不会验证存根上的任何内容。

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 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.

Writing the mocks is often more work
than writing the classes themselves

For mocks or stubs, consider using an isolation (mocking) framework.

in the end, all my tests really verify
is the most basic skeleton of the
behavior: that "if" and "for"
statements still work

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.

场罚期间 2024-08-14 06:18:32

编写集成测试在这里是一个可行的选择,但不应取代单元测试。但既然你说你的写作是对自己的模拟,我建议使用隔离框架(又名模拟框架),我非常确定它也适用于你的环境。

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.

勿忘初心 2024-08-14 06:18:32

鉴于您在一个帖子中提出了多个问题,我将一一回答。

如何为主要面向服务的应用程序编写有用的单元测试?

不要依赖“主要面向服务的应用程序”的单元测试!是的,我用一句话说了这句话。这些类型的应用程序旨在做一件事:集成服务。因此,更紧迫的是,您需要编写集成测试而不是单元测试,以确保集成正常工作。

我没有看到任何使用模拟实际上有用的情况。

模拟非常有用,但我不会在控制器上使用它们。控制器应该包含在集成测试中。单元测试可以涵盖服务,但如果测试量减慢了项目的速度,将它们作为单独的模块可能是明智的。

想法?

对我来说,我倾向于考虑一些事情:

  1. 我的应用程序在做什么?
  2. 执行系统级/集成测试的成本有多高?
  3. 我可以将我的应用程序分成可以单独测试的模块吗?

在您提供的场景中,我想说您的应用程序是许多服务的集成。因此,我会严重依赖集成测试而不是单元测试。我敢打赌,您编写的大部分模拟都是针对 http 相关类等的。

我更喜欢集成/系统级测试,原因如下:

  1. 在当今“快速发展”的时代,重构昨天的设计的速度越来越快。集成测试根本不关心实现细节,因此这有利于快速更改。动态语言正在如火如荼地进行,使得模拟变得更加危险/脆弱。使用静态语言,模拟会更安全,因为如果您的测试试图删除不存在或拼写错误的方法名称,则它们将无法编译。
  2. 为了达到相同的覆盖水平,集成测试中编写的代码量通常比单元测试中编写的代码量少 60%,因此开发时间更短。 “是的,但是运行集成测试需要更长的时间......”这就是您需要务实的地方,直到它实际上减慢了您运行集成测试的速度。
  3. 集成测试会发现更多错误。模拟通常是人为的,它使开发人员脱离了他们的更改将对整个应用程序产生的影响的现实。在 100% 单元测试覆盖率的“安全网”下,与集成测试相比,我允许更多的错误进入生产环境。
  4. 如果我的应用程序的集成测试很慢,那么我还没有将其分成单独的模块。这通常是早期的一个迹象,表明我需要进行一些提取以进行分离。
  5. 集成测试为您做的不仅仅是达到代码覆盖率,它们也是性能问题或网络问题等的指标。

Being that you've posted several questions in one I'll answer them one by one.

How do I write useful unit tests for a mostly service-oriented app?

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.

I'm not seeing any cases where using mocks is actually useful.

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.

Thoughts?

For me, I tend to think about a few things:

  1. What is my application doing?
  2. How expensive would it be to perform system level / integration tests?
  3. Can I split my application up into modules that can be tested separately?

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:

  1. In this day and age of "moving fast", re-factoring the designs of yesterday happens at an ever increasing rate. Integration tests aren't concerned about implementation details at all so this facilitates rapid change. Dynamic languages are in full swing making mocks even more dangerous / brittle. With a static lang, mocks are much safer because your tests won't compile if they're trying to stub out a non existent or misspelled method name.
  2. The amount of code written in an integration test is usually 60% less than the amount of code written in a unit test to achieve the same level of coverage so development time is less. "Yes but it takes longer to run integration tests..." that's where you need to be pragmatic until it actually slows you down to run integration tests.
  3. Integration tests catch more bugs. Mocking is often contrived and removes the developer from the realities of what their changes will do to the application as a whole. I've allowed way more bugs into production under the "safety net" of 100% unit test coverage than I would have with integration tests.
  4. If integration testing is slow for my application then I haven't split it up into separate modules. This is often an indicator early on that I need to do some extracting into separation.
  5. Integration tests do way more for you than reach code coverage, they're also an indicator of performance issues or network problems etc.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文