如何测试直接在外部API上运行的Java应用程序
从 Ruby 世界回来后,我在 Java 中进行 TDD 时遇到了一些问题。最大的问题是当我的应用程序仅与外部 API 通信时。
假设我只想从 Google 日历中获取一些数据,或者从某个 Twitter 用户获取 5 条推文并显示它。
在 Ruby 中,我没有任何问题,因为我可以直接在测试中对 API 库进行猴子修补,但在 Java 中没有这样的选项。
如果我从 MVC 角度考虑这一点,我的模型对象将通过某个库直接访问 API。问题是,这是糟糕的设计吗?我是否应该始终将任何 API 库包装在某个接口中,以便可以在 Java 中模拟/存根它?
因为当我想到这一点时,该接口的唯一目的是模拟(请不要因为这么说而杀了我)猴子补丁。这意味着每当我使用任何外部资源时,我都必须将每一层包装在可以被删除的接口中。
# do I have to abstract everything just to do this in Java?
Twitter.stub!(:search)
现在你可能会说我应该总是抽象出接口,这样我就可以将底层更改为其他任何层。但如果我正在编写 Twitter 应用程序,我不会将其更改为 RSS 阅读器。
是的,我可以添加例如 Facebook,然后拥有界面就有意义了。但是,当没有其他资源可以替代我正在使用的资源时,我仍然必须将所有内容包装在接口中以使其可测试。
我是否遗漏了什么,或者这只是 Java 世界中的一种测试方法?
After comming from Ruby world, I'm having little problems doing TDD in Java. The biggest issue is when I have application that is just communicating with external API.
Say I want to just fetch some data from Google Calendar, or 5 tweets from some Twitter user and display it.
In Ruby, I don't have any problems, because I can monkey-patch the API library in tests directly, but I have no such option in Java.
If I think about this in terms of MVC, my model objects are directly accessing the API through some library. The question is, is this bad design? Should I always wrap any API library in some interface, so I can mock/stub it in Java?
Because when I think about this, the only purpose of that interface would be to simulate (please don't kill me for saying this) the monkey-patch. Meaning that any time I use any external resource, I have to wrap each layer in interface that can be stubbed out.
# do I have to abstract everything just to do this in Java?
Twitter.stub!(:search)
Now you might say that I should always abstract away the interface, so I can change the underlying layer to anything else. But if I'm writing twitter app, I'm not going to change it to RSS reader.
Yes, I can add for example Facebook and then it would make sense to have interface. But when there is no other resource that can be substituted for the one I'm using, than I still have to wrap everything in interfaces to make it testable.
Am I missing something, or is this just a way to test in the Java world?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
使用接口通常是 Java 中的良好实践。有些语言具有多重继承,其他语言具有鸭子类型,Java具有接口。这是该语言的一个关键特性,它允许我
因此,接口是一个您应该普遍接受的概念,然后您将在这种情况下获得好处,您可以用模拟对象替换您的服务。
关于 Java 最佳实践的最重要的书籍之一是 Joshua Bloch 所著的《Effective Java》。我强烈建议你阅读它。在这种情况下,最重要的部分是第 52 条:通过接口引用对象。引用:
如果你更进一步(例如使用依赖注入时),你甚至不会调用构造函数。
转换语言的关键问题之一是你也必须转换思维方式。当你用 y 语言思考时,你无法有效地对 x 语言进行编程。如果不使用指针,就无法有效地进行 C 语言编程;如果没有鸭子类型,则无法有效地进行 Ruby 编程;如果没有接口,就无法有效地进行 Java 编程。
Using interfaces is just generally good practice in Java. Some languages have multiple inheritance, others have duck typing, Java has interfaces. It's a key feature of the language, it lets me use
So interfaces are a concept you should embrace in general, and then you would reap the benefits in situations like this where you could substitute your services by mock objects.
One of the most important books about Java best practices is Effective Java by Joshua Bloch. I would highly suggest you to read it. In this context the most important part is Item 52: Refer to objects by their interfaces. Quote:
And if you take things even further (e.g. when using dependency injection), you aren't even calling the constructor.
One of the key problems of switching languages is that you have to switch the way of thinking too. You can't program language x effectively while thinking in language y. You can't program C effectively without using pointers, Ruby not without duck typing and Java not without Interfaces.
包装外部 API 是我执行此操作的方法。
因此,正如您已经说过的,您将拥有一个接口和两个类:真实的类和虚拟的实现。
是的,从某些确实特定的服务(例如 Twitter)的角度来看,这似乎不合理。但是,这样您的构建过程就不会依赖于外部资源。依赖外部库并不是那么糟糕,但是让您的测试依赖于网络上存在或不存在的实际数据可能会扰乱构建过程。
最简单的方法是用接口/类对包装 API 服务,并在整个代码中使用它。
Wrapping the external API is the way I would do this.
So, as you already said, you would have an interface and two classes: the real one and the dummy implementation.
Yes, it may seem unreasonable from the perspective of some services indeed being specific, like Twitter. But, this way your build process doesn't depend on external resources. Depending on external libraries isn't all that bad, but having your tests depend on actual data present or not present out there on the web can mess up the build process.
The easiest way is to wrap the API service with your interface/class pair and use that throughout your code.
我知道您想要的是模拟对象。
正如您所描述的,生成对象的“测试版本”的方法之一是实现并使用通用接口。
但是,您缺少的是简单地扩展该类(前提是它没有声明为final)并覆盖您想要模拟的方法。 (注意:这样做的可能性就是为什么库将其类声明为 Final 被认为是不好的形式 - 它会使测试变得相当困难。)
有许多 Java 库旨在促进 Mock 对象的使用- 您可以查看 Mockito 或 EasyMock。
I understand that what you want are Mock objects.
As you described it, one of the ways one can generate "test versions" of objects is by implementing a common interface and using it.
However, what you are missing is to simply extend the class (provided that it is not declared
final
) and override the methods that you want to mock. (NB: the possibility of doing that is the reason why it is considered bad form for a library to declare its classes final - it can make testing considerably harder.)There is a number of Java libraries that aim in facilitating the use of Mock objects - you can look at Mockito or EasyMock.
Mockito 更方便,就像你的 ruby 模拟一样。
Mockito is more handy and like your ruby mocks.
您可以用Java“猴子修补”API。 Java 语言本身没有提供具体的方法来执行此操作,但 JVM 和标准库提供了。在 Ruby 中,开发人员可以使用 Mocha 库来实现此目的。在 Java 中,您可以使用 JMockit 库(由于旧的模拟工具的限制,我创建了该库) 。
下面是一个 JMockit 测试示例,相当于 Mocha 文档中提供的
test_should_calculate_value_of_unshipped_orders
测试:You can "monkey-patch" an API in Java. The Java language itself does not provide specific means to do it, but the JVM and the standard libraries do. In Ruby, developers can use the Mocha library for that. In Java, you can use the JMockit library (which I created because of limitations in older mocking tools).
Here is an example JMockit test, equivalent to the
test_should_calculate_value_of_unshipped_orders
test available in Mocha documentation: