【问题标题】:Testing interactions with external services测试与外部服务的交互
【发布时间】:2011-09-18 16:36:18
【问题描述】:

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

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

控制器方法

在其他框架中,我会做的是在控制器中使用构造函数注入并在我的测试中传入一个模拟队列;但是,使用 Play!控制器是静态的,这意味着我不能在测试中执行new MyController(mockedQueue)

我可以使用 Google Guice 并在我的控制器中的静态字段上添加 @Inject 注释,但这对我来说感觉不太好,因为这要么意味着我必须公开该字段才能在测试,或者我必须在测试中使用容器。我更喜欢使用构造函数注入,但是 Play!似乎没有帮助。

模型方法

人们常说您的逻辑应该在您的模型中,而不是在您的控制器中。这就说得通了;但是,我们不是在 Ruby 中,让您的实体与外部服务(电子邮件、消息队列等)交互的可测试性比在动态环境中要少得多,在动态环境中,您可以用模拟的方式替换 MessageQueue 静态调用随意实例。

如果我让我的实体调用队列,那如何测试?

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

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

【问题讨论】:

    标签: java unit-testing testing playframework


    【解决方案1】:

    正如我所见,没有一个干净的解决方案。

    您可以使用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 可能是一个可行的选择。

    祝你好运!

    【讨论】:

    • ServiceFactory 方法的一个潜在问题是该字段何时被初始化;因为它是一个静态字段,所以 getInstance 可能会在您有机会替换实例之前被初始化程序调用。只是大声思考,并没有真正验证这一点。
    • 其实例子中ServiceFactory是一个Singleton。您不会替换 ServiceFactory 本身,而是替换它“产生”的 QueueService。
    • 是的,我明白了;但是,由于该字段是静态的,因此有可能在您能够替换从getInstance 返回的任何值之前对其进行初始化。您通常无法控制静态初始化程序何时执行,因此它可能发生在您的测试设置发生之前。
    • 好的,我理解您的担忧,但我们实际上并不是在模拟 ServiceFactory。 getInstance() 方法将始终返回相同的 Singleton 实例(ServiceFactory 实例),无论我们在测试还是在生产代码中调用它。我们正在配置ServiceFactory,所以我们可以替换方法getQueueService()返回的值。
    • 我现在明白了,谢谢。 ServiceFactory 有效地返回单例似乎有点奇怪,但我可以理解理由。谢谢,是我的错。
    【解决方案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 编写单元测试用例,并为 Users 控制器 save 方法编写功能测试用例。

    【讨论】:

    • 也许我不清楚。您将如何在测试中用模拟实例替换 new QueueService()?或者您是说您要对QueueService 进行单元测试,但只对UserQueueService 之间的交互进行功能测试?
    • 如果是这样的话,很好;但在我处理过的大多数其他框架中,测试两者之间的交互是完全可行的,而不需要实际可用的实时队列。
    【解决方案3】:

    编辑:如前所述扩展答案尚不清楚

    第一个想法是将队列的引用添加到模型中,因为您有一个 POJO 并可以访问构造函数。正如您在下面的 cmets 中提到的,在考虑 Hibernate 水合实体时,Model 方法存在问题,这将丢弃这一点。

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

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

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

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

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

    希望这次更新能更清楚地说明我在回答时的想法。

    【讨论】:

    • 鸡和鸡蛋。那么工厂从哪里来?单身?
    • 鸡和蛋? :|您可能有一个静态工厂方法,在单例或实用程序类或某处。它的唯一目的是返回您需要的 Queue 对象。我的意思是,你还想用什么其他方式来做呢?恐怕我没有看到问题,或者我不明白你的反对意见。
    • 在任何其他框架中,我都会使用构造函数注入(有或没有容器)。使用单例或工厂方法仅用于隐藏类真正具有的依赖项。
    • 但是 Model 有构造函数......这就是你想要放置它的地方......并且实体是一个 POJO,绝对可测试......
    • 除非它是通过 Hibernate 补水的模型。
    【解决方案4】:

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

    稍微详细一点:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多