2011-09-18 89 views
8

先决条件:我使用的是最新版本的Play! framework和Java版本(不是Scala)。测试与外部服务的交互

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

的控制方法

在其他框架,我就做了是使用构造器注入到控制器,并通过在我的测试中嘲笑队列;但是,与Play!控制器是静态的,这意味着我不能在我的测试中做new MyController(mockedQueue)

我可以使用Google Guice并将一个@Inject注释放在控制器的静态字段中,但对我来说这并不好,因为这意味着我必须将该字段公开为在测试中被替换,或者我必须在测试中使用容器。我更喜欢使用构造函数注入,但玩!似乎并没有促进这一点。

模型方法

人们常常说你的逻辑应该是在你的模型,而不是你的控制器。这就说得通了;然而,我们不在Ruby这里,让你的实体与外部服务(电子邮件,消息队列等等)交互...比在动态环境中可测试性要差得多,在这种环境中,你可以用一个模拟实例替换你的静态调用将。

如果我让实体呼叫进入队列,那么可测试性如何?

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

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

回答

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方法,你可以创建一个接口和配置实现在你的测试中上课。

另一种选择是使用ØPowerMock为嘲讽的静态方法。我以前用过,在大多数情况下它工作得相当好。只是不要过度使用它,或者你在维护地狱......

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

祝你好运!

+0

“ServiceFactory”方法存在的一个*潜在问题是该字段将被初始化;因为它是一个静态字段,所以'getInstance'可能会在您有机会替换实例之前被初始化程序调用。只是大声思考,没有实际验证这一点。 –

+0

实际上,在这个例子中,ServiceFactory是一个Singleton。您不会替换ServiceFactory本身,而是替换它生成的QueueService。 –

+0

是的,我明白了;但是,由于该字段是静态的,因此在您能够替换从getInstance返回的任何值之前,它将被初始化。您通常无法控制执行静态初始化程序的时间,因此可能会在测试设置发生之前发生。 –

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和写入功能测试用例的用户控制器保存方法。

+0

也许我不清楚。你将如何用测试中的模拟实例替换'新的QueueService()'?还是你说你会单元测试'QueueService',但是只对'User'和'QueueService'之间的交互进行功能测试? –

+0

如果这是要走的路,很好;但在我处理的大多数其他框架中,测试两者之间的交互并不要求存在实际的实时队列是完全可行的。 –

2

编辑:扩展的答案以前并不清楚

第一个想法是将参考队列添加到模型中,当你有一个POJO和访问构造函数。正如你在下面的评论中提到的那样,当考虑Hibernate水化实体时,模型方法是有问题的,这会丢弃这个实体。

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

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

我使用这种分离为从测试视图的点更容易(我),以测试通过Selenium控制器和用于服务做单独的测试(使用JUnit)。

在您的情况下,此服务将包括对您提到的队列的引用。

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

希望此更新能够更清楚地说明我回答时记住的内容。

+0

鸡和鸡蛋。那么工厂从哪里来?单身? –

+0

鸡和鸡蛋? :|您可能有一个静态工厂方法,在单例或实用程序类中或某处。它唯一的目标是返回你需要的Queue对象。我的意思是,你想要做什么其他方式?恐怕我没有看到问题,或者我不明白你的反对意见。 –

+0

在任何其他框架中,我会使用构造函数注入(有或没有容器)。使用单例或工厂方法只能隐藏类真正具有的依赖关系。 –

1

这也许不是你要找的东西,但在我的当前项目,我们已经解决了通过集成测试类型的测试和JMS建立与本地队列和消息传递桥。

在稍微更详细:

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

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