如何对需要Web服务调用的类进行单元测试?


21

我正在尝试测试一个调用某些Hadoop Web服务的类。代码几乎是以下形式:

method() {
    ...use Jersey client to create WebResource...
    ...make request...
    ...do something with response...
}

例如,有一个创建目录方法,一个创建文件夹方法等。

鉴于代码正在处理我无法控制的外部Web服务,我该如何对其进行单元测试?我可以尝试模拟Web服务客户端/响应,但这违反了我最近看到的指导方针:“不要模拟您不拥有的对象”。我可以设置一个虚拟Web服务实现-仍然构成“单元测试”还是将其作为集成测试?只是不可能在如此低的水平上进行单元测试-TDD从业者将如何做到这一点?


5
您在哪里看到了关于不嘲笑您不拥有的东西的指导?这似乎是您应该嘲笑事物的重要原因……
Thomas Owens


1
@ChrisCooper:我可以指出最后一个链接已经非常过时了(从2007年开始)。从那时起,很多事情都发生了变化。从帖子中我得到的感觉是,当您不得不实际实现行为而不是简单地使用
模拟

Answers:


41

我认为,如果这是单元测试而不是集成测试,则应该模拟webservice调用。

您的单元测试不应测试外部Web服务是否正常运行,或者与之集成是否正确。不必太拘泥于TDD,请注意将单元测试转换为集成测试的副作用是运行速度可能较慢,而您需要快速的单元测试。

另外,如果Web服务暂时关闭或工作不正常,这是否会导致单元测试失败?似乎不正确。您的单元测试应该仅出于以下原因而失败:如果该“单元”中的代码中存在错误。

与代码相关的唯一部分是...do something with response...。模拟其余部分。


2
请记住,在不可避免的更改过程中,您必须保持模拟对象签名并返回与Hadoop Web服务生成的值同步的值。
pcurry 2013年

测试像Hadoop这样的现成组件很少值得。但是,如果您要调用另一个团队或组织提供的自定义Web服务,则可能需要自卫编写一个测试。这样,当出现问题时,您可以快速检查问题是代码还是Web服务。这不是自动运行的单元测试。这是根据需要运行的诊断。
凯文·克莱恩

@kevincline我完全同意您提出的测试的必要性,的确,我在日常工作中编写了这些测试,并证明自己很有用。但是从定义上讲,它们不是单元测试,而是问题所在:)考虑一下:如果这是单元测试,并且由于更改了Web服务而导致代码失败,那么您正在测试的“单元”是什么?到底是什么失败了?您并不是在按照单元测试的要求进行隔离测试。
Andres F.

1
@AndresF:我想我们是在暴力的协议:“这[诊断]不是一个单元测试...”
凯文·克莱恩

@kevincline对!我看错了您的评论,对不起!
Andres F.

5

当您进行单元测试时,我不同意“不要模拟您不拥有的对象”。

嘲弄存在的目的是,将存在我们不会拥有的模块,库,类。

我对您的方案的建议是模拟Web服务调用。

设置模拟程序,使其将数据返回到您的模块。
确保涵盖所有情况,例如,当返回的数据为空,返回的数据有效时等。

对于您拥有的代码,作为开发人员的责任是确保所编写的代码在所有情况下均按预期方式执行。


1

我会在此测试中使用EasyMock之类的东西。模拟框架是删除类的外部依赖关系的理想方法,并且可以在测试过程中完全控制外部依赖关系的结果。稍微扩展一下示例:

class WebClass {

private WebServiceInterface webserviceInterface;

    void method(){
        R result = webServiceInterface.performWebServiceCall();
        ... do something with result
    }

    public void setWebServiceInterface(WebServiceInterface webServiceInterface){
        this.webServiceInterface = webServiceInterface;
    }
}


interface WebServiceInterface {

   R performWebServiceCall();

}


class WebClassTest {

private WebServiceInterface mock;    
private R sampleResult = new R();

    @Before
    public void before(){
        mock = EasyMock.createMock(WebServiceInterface.class);
    }


    @Test
    public void test() {
        WebClass classUnderTest = new WebClass();
        EasyMock.expect(mock.performWebServiceCall()).andReturn(sampleResult);
        classUnderTest.setWebServiceInterface(mock);
        classUnderTest.method();
        EasyMock.verify(mock);
    }
}

您需要做的第一件事是在类中提取逻辑,在该类中,您使用Jersey来获取WebResource并将Web服务调用到一个单独的类中。为此类创建一个接口将允许您创建一个模拟,然后您可以指示该行为。

创建此接口后,您可以使用EasyMock创建一个模拟,它将根据测试用例返回指定的对象。上面的示例简化了如何构建基本的模拟测试以及界面如何工作。

有关模拟框架的更多信息,请参见此问题。另外,此示例假定使用Java,但所有语言都提供了模拟框架,尽管实现方式有所不同,但它们的工作方式通常相同


1

在这种情况下,可以接受模拟,但您不需要。代替单元测试method(),而是仅对处理响应的部分进行单元测试。

提取一个需要ResponseData(适当的类型)的函数,然后执行操作。

现在不用模拟,而只是构造一个ResponseData对象并将其传入。

您可以将服务的调用留给完整的集成测试-这将涵盖method()全部


0

我做了什么,并且有效:

  1. 通过代理使所有代码调用Web服务。
  2. 代理调用一个静态知道我们是否正在使用代理的类,并相应地进行重定向。嘲笑只是HashMaps,它为每个请求返回给定的答复。
  3. 按此顺序多次运行测试:

3.1首先,所有Web服务都经过测试。在每台计算机上,甚至在开发人员的计算机上。这些是真正的Web服务,但是在开发环境中运行。这意味着Web服务永远不会宕机或回复错误的值,因为否则每个开发人员都会抱怨自己无法编译。

3.2然后运行应用程序内部的所有单元测试。这意味着所有的Web服务都将在与3.1相同的测试中进行模拟和测试(除非它们也应该通过,否则模拟是错误的),并由真实的应用程序调用,就好像它们确实在被使用一样。如果模拟错误,则可以在3.1中运行测试,并将这些(请求,答复)值记录在HashMap中。

3.3然后运行与3.2相同的测试,但是这次针对开发环境中运行的实际Web服务。

在完成所有这些操作之后,对于实际的生产环境,您只需要为每个Web服务提供真实的地址即可。希望这不需要对配置进行太多更改。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.