如何测试直接在外部API上运行的Java应用程序

如何测试直接在外部API上运行的Java应用程序,java,ruby,testing,junit,rspec,Java,Ruby,Testing,Junit,Rspec,从Ruby world加入后,我在用Java进行TDD时遇到了一些小问题。最大的问题是当我的应用程序只是与外部API通信时 假设我只想从谷歌日历中获取一些数据,或者从某个Twitter用户那里获取5条推文并显示出来 在Ruby中,我没有任何问题,因为我可以直接在测试中对API库进行猴子补丁,但在Java中我没有这样的选项 如果我从MVC的角度考虑这一点,我的模型对象是通过某个库直接访问API的。问题是,这个设计不好吗?我是否应该总是将任何API库包装在某个接口中,以便在Java中模拟/存根它?

从Ruby world加入后,我在用Java进行TDD时遇到了一些小问题。最大的问题是当我的应用程序只是与外部API通信时

假设我只想从谷歌日历中获取一些数据,或者从某个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世界中进行测试的一种方式?我知道您想要的是什么

正如您所描述的,生成对象的“测试版本”的方法之一是实现一个公共接口并使用它

但是,您缺少的是简单地扩展该类(前提是它未声明为
final
),并重写要模拟的方法。(注意:这样做的可能性是库声明其类为final被认为是不好的形式的原因——它会使测试变得相当困难。)


有许多Java库旨在促进模拟对象的使用—您可以查看或。

包装外部API是我的方法

因此,正如您已经说过的,您将有一个接口和两个类:真实的和虚拟的实现

是的,从某些特定服务的角度来看,这似乎是不合理的,比如Twitter。但是,通过这种方式,您的构建过程不依赖于外部资源。依赖外部库并不是那么糟糕,但是让您的测试依赖于web上存在或不存在的实际数据可能会打乱构建过程


最简单的方法是用接口/类对包装API服务,并在整个代码中使用它。

在Java中使用接口通常是很好的做法。有些语言有多重继承,有些有duck类型,Java有接口。这是语言的一个关键特性,它让我可以使用

  • 在不同的环境和条件下,课程的不同方面
  • 在不更改客户端代码的情况下对同一合同进行不同的实现
  • 因此,接口通常是一个您应该接受的概念,然后在这样的情况下,您可以用模拟对象替代您的服务,从而获得好处

    关于Java最佳实践的最重要书籍之一是。我强烈建议你读一下。在此上下文中,最重要的部分是第52项:通过对象的接口引用对象。引述:

    更一般地说,您应该倾向于使用接口,而不是 类来引用对象如果存在适当的接口类型,则应使用接口声明参数、返回值、变量和字段 类型。真正需要引用对象类的唯一时间是 使用构造函数创建它

    如果你把事情做得更进一步(例如,当使用依赖注入时),你甚至不会调用构造函数


    转换语言的一个关键问题是你也必须转换思维方式。当您使用语言y思考时,无法有效地编程语言x。如果不使用指针,就无法有效地编程C,Ruby不能不使用duck类型,Java不能不使用接口。

    Mockito更方便,并且与Ruby Mock类似。

    您可以用Java“monkey patch”API。Java语言本身并没有提供具体的实现方法,但JVM和标准库提供了。在Ruby中,开发人员可以使用该库来实现这一点。在Java中,您可以使用这个库(由于旧的模拟工具的限制,我创建了这个库)

    下面是一个示例JMockit测试,相当于
    test\u应\u计算\u未发货订单的\u值\u
    test,可在以下位置获得:

    @测试
    public void应计算未发货订单()的价值
    {
    最终订单号=新订单号();
    最终列表订单=asList(订单号,新订单号(),新订单号());
    新的非严格要求(Order.class)
    {{
    Order.findAll();结果=订单;
    anOrder.getTotalCost();结果=10;
    }};
    assertEquals(30,Order.unshippedValue());
    }
    
    是的,我可以扩展它,但这需要另一个层将“API”对象(测试中的模拟版本)传递给模型。但是我想这是没有办法的。在这种情况下,您可以使用工厂方法创建对象,也可以使用反射替换模型中的私有字段。当然(很抱歉延迟)!我不认为有效Java中的“条目52”说开发人员应该为每个类创建单独的Java接口,正如您所暗示的那样。注意“如果存在适当的接口类型…”的部分。当它们不存在时,可以使用隐式类接口。硒
       @Test
       public void shouldCalculateValueOfUnshippedOrders()
       {
          final Order anOrder = new Order();
          final List<Order> orders = asList(anOrder, new Order(), new Order());
    
          new NonStrictExpectations(Order.class)
          {{
             Order.findAll(); result = orders;
             anOrder.getTotalCost(); result = 10;
          }};
    
          assertEquals(30, Order.unshippedValue());
       }