测试与外部服务的交互

时间:2011-09-18 16:36:19

标签: java unit-testing testing playframework

先决条件:我使用的是最新版本的Play! framework和Java版本(不是Scala)。

我需要在创建用户时将消息发布到消息队列,并且我想测试该行为。我的问题是让这个很容易测试。

控制器方法

在其他框架中,我要做的就是在控制器中使用构造函数注入,并在我的测试中传入一个模拟队列;然而,玩Play!控制器是静态的,这意味着我在测试中无法new MyController(mockedQueue)

我可以使用Google Guice并在我的控制器中的静态字段上添加@Inject注释,但这对我来说并不是很好,因为它或者意味着我必须公开该字段才能被替换在测试中,或者我必须在我的测试中使用容器。我更喜欢使用构造函数注入,但玩!似乎不方便。

模型方法

通常说你的逻辑应该在你的模型中,而不是你的控制器。那讲得通;但是,我们在这里不是Ruby,让你的实体与外部服务(电子邮件,消息队列等等)进行交互的可测性远远低于动态环境,在这种环境中你可以用你的MessageQueue静态调用替换随意的模拟实例。

如果我让我的实体呼叫到队列,那该怎么可测试?

当然,如果我进行端到端的集成测试,这两种情况都是不必要的,但我不需要为我的测试运行而需要一个消息队列或SMTP服务器。

所以我的问题是:我如何为我的游戏建模!控制器和/或模型,以促进测试与外部服务的交互?

4 个答案:

答案 0 :(得分:3)

正如我所看到的,对此没有一个干净的解决方案。

您可以使用Abstract Factory作为依赖项。这个工厂可以为它生成的对象设置setter方法。

public class MyController {
    ...
    private static ServiceFactory serviceFactory = ServiceFactory.getInstance();
    ...
    public static void action() {
        ...
        QueueService queue = serviceFactory.getQueueService();
        ...
    }

}

您的测试将如下所示:

public void testAction() {
    QueueService mock = ...
    ...
    ServiceFactory serviceFactory = ServiceFactory.getInstance();
    serviceFactory.setQueueService(mock);
    ...
    MyController.action();
    verify(mock);
}

如果您不想公开工厂的setter方法,可以在测试中创建一个接口并配置实现类。

另一种选择是使用o PowerMock来模拟静态方法。我之前使用过它,对大多数情况来说效果相对较好。只是不要过度使用它,或者你在维护地狱......

最后,由于您愿意在您的应用程序中使用Guice,this可能是一个可行的选择。

祝你好运!

答案 1 :(得分:2)

我有点困惑。你可以调用另一个类的方法

public class Users extends Controller {
    public static void save(@Valid User user) {
    //check for user validaton
    user = user.save();
    QueueService queueService = new QueueSerice();
    queueService.publishMessage(user);
    }
}

您可以使用模拟为 QueueService 编写单元测试用例,并为用户控制器保存方法编写功能测试用例。

答案 2 :(得分:2)

编辑:扩展答案,因为以前不清楚

第一个想法是将对Queue的引用添加到Model中,因为您有POJO并且可以访问构造函数。正如你在下面的评论中提到的那样,在考虑Hibernate为实体提供保护时,模型方法是有问题的,这会丢弃它。

第二种方法是将对Queue的引用添加到Controller。现在,这似乎是一个坏主意。除了你提到的公共成员问题,我相信控制器背后的想法是检索请求的参数,验证它们是否正确(checkAuthenticity,验证等),发送要处理的请求然后准备响应。

这里的“密钥”是“发送要处理的请求”。在某些情况下,如果它很简单,我们可以在Controller中完成这项工作,但在其他情况下,最好使用“服务”(以某种方式调用它),在该服务中,您可以使用给定数据完成所需的工作。

我使用这种分离,从测试的角度来看,对我来说,通过Selenium测试控制器更容易,并为服务进行单独的测试(使用JUnit)。

在这种情况下,此服务将包含对您提及的队列的引用。

关于如何初始化,这将取决于。你可以创建一个单例,每次通过构造函数初始化它等等。在你的特定场景中,这可能取决于与初始化队列服务相关的工作:如果很难你可能想要一个带有Factory方法的Singleton来检索服务(并且可以在测试中进行模拟)并将其作为参数传递给Service对象的构造函数。

希望这个更新能够澄清我回答时的想法。

答案 3 :(得分:1)

这可能不是你想要的,但在我目前的项目中,我们通过集成测试和带有本地队列和消息传递桥的JMS设置解决了这种类型的测试。

稍微详细一点:

  • 您的代码始终向/从本地队列发送/读取消息,即本地应用服务器上的队列(不在外部系统上)。
  • 消息传递桥在需要时将本地队列连接到外部服务的队列,例如,在生产或手动测试环境中。
  • 集成测试会创建新用户(或任何您要测试的用户),然后从本地队列中读取预期的消息。在这种情况下,消息传递桥未激活。

在我的项目中,我们使用SoapUI来执行这些测试,因为被测系统是基于SOAP的集成平台,SoapUI具有良好的JMS支持。但它也可以是一个简单的JUnit测试,它会执行测试并在之后从本地JMS队列中读取。