4

我有一个 EJB 向 JMS 队列发送消息并等待它的回复。我想测试EJB,很容易使用OpenEJB来做EJB的JUnit测试。但问题是这个 EJB 将等待 JMS 响应继续处理。

虽然我可以在我的 junit 代码中发送消息,但因为 EJB 仍在进行中,所以在 EJB 完成之前我无法运行它。

第二种解决方案是我可以初始化一个 MDB 来监听和回复来自 EJB 的 JMS 消息,但问题是 MDB 必须在 src\main\java 中,而不能在 src\test\java 中。问题是这只是一个测试代码,我不应该将它打包到生产环境中。(我使用Maven)

或者我应该使用模拟对象?

4

5 回答 5

6

你在正确的轨道上。处理这个问题的方法很少。下面是一些使用 OpenEJB 和 Maven 进行单元测试的技巧。

测试豆

您可以编写各种 EJB 和其他测试实用程序并部署它们。您所需要的只是ejb-jar.xml测试代码,如下所示:

  • src/main/resources/ejb-jar.xml (正常的)

  • src/test/resources/ejb-jar.xml (测试豆)

像往常一样,ejb-jar.xml文件只需要包含<ejb-jar/>,仅此而已。它的存在只是告诉 OpenEJB 检查类路径的那部分并扫描它以查找 bean。扫描整个类路径非常慢,所以这只是加快速度的惯例。

测试用例注入

使用上述内容,src/test/resources/ejb-jar.xml您可以非常轻松地添加该仅测试 MDB 并设置它以以 TestCase 需要的方式处理请求。但它src/test/resources/ejb-jar.xml也开辟了一些其他有趣的功能。

您可以TestCase通过声明对您需要的任何 JMS 资源的引用并注入它们来让自己做到这一点。

import org.apache.openejb.api.LocalClient;

@LocalClient
public class ChatBeanTest extends TestCase {

    @Resource
    private ConnectionFactory connectionFactory;

    @Resource(name = "QuestionBean")
    private Queue questionQueue;

    @Resource(name = "AnswerQueue")
    private Queue answerQueue;

    @EJB
    private MyBean myBean;


    @Override
    protected void setUp() throws Exception {
        Properties p = new Properties();
        p.put(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
        InitialContext initialContext = new InitialContext(p);

        initialContext.bind("inject", this); // here's the magic!
    }
}

现在,您距离能够响应测试用例本身的 JMS 消息只有一个线程。您可以启动一个小可运行程序,它将读取一条消息,发送您想要的响应,然后退出。

也许是这样的:

public void test() throws Exception {

    final Thread thread = new Thread() {
        @Override
        public void run() {
            try {
                final Connection connection = connectionFactory.createConnection();

                connection.start();

                final Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

                final MessageConsumer incoming = session.createConsumer(requestQueue);
                final String text = ((TextMessage) incoming.receive(1000)).getText();

                final MessageProducer outgoing = session.createProducer(responseQueue);
                outgoing.send(session.createTextMessage("Hello World!"));

            } catch (JMSException e) {
                e.printStackTrace();
            }
        }
    };
    thread.setDaemon(true);
    thread.start();

    myBean.doThatThing();

    // asserts here...
}

备用描述符

如果您确实想使用 MDB 解决方案并且只想为一个测试而不是所有测试启用它,您可以在一个特殊src/test/resources/mockmdb.ejb-jar.xml文件中定义它并在需要它的特定测试用例中启用它。

有关如何启用该描述符以及备用描述符的各种选项的更多信息,请参阅此文档。

于 2011-09-16T17:37:42.973 回答
2

我认为你应该为此使用模拟。如果您正在向真正的 JMS 服务器发送消息、监听它们、回复它们等,那么您所做的不是单元测试。我不打算讨论应该叫什么,但我认为单元测试不应该与实时数据库、消息队列等对话,这是普遍接受的。

于 2011-09-16T14:30:27.240 回答
2

如果我对您的问题的理解是正确的 - 让 EJB 发送 JMS 消息然后等待响应是一个糟糕的设计,实际上与 EJB 的整个想法相矛盾。

您发送一条 JMS 消息,然后忘记它。您有一个 MDB 来接收消息。如果 EJB 依赖于响应,则 JMS 不是要走的路,而是使用另一个 EJB。

要测试发送,模拟 JMS 类,单独测试 MDB。

EJB 是为同步任务设计的,JMS 是为异步任务设计的——如果您必须与外部系统进行异步通信,我建议您在此之后设计您的系统,并执行适当的异步流程。坐下来等待 JMS 回复的 EJB 充其量只是一个丑陋的 hack,不会为您的系统设计增加任何好处。

于 2011-09-16T15:16:58.640 回答
1

感谢大卫的回答,这就是我想要的。我知道单元测试不应该依赖于 JMS 服务器等其他外部资源。但是如果我使用 Maven + OpenEJB,我仍然可以让测试代码在一个封闭的环境中。它可以帮助进行外部资源依赖的自动测试,特别是对于一些不易重构的旧程序。

如果您在 initialContext.bind("inject", this) 中看到以下错误消息

确保该类已使用 @org.apache.openejb.api.LocalClient 进行注释,并且已成功发现和部署。

一个参考是http://openejb.apache.org/3.0/local-client-injection.html,但添加“openejb.tempclassloader.skip=annotations”对我不起作用。请检查此文档OpenEJB Local Client Injection Fails。已经有补丁了,我想它会在 OpenEJB 3.1.5 或 4.0 中修复

于 2011-09-17T15:04:16.353 回答
0

此外,我发现将 MDB 中的逻辑分解为不同的类是最佳实践。这将您的业务逻辑与 MDB 隔离开来,并允许您以不止一种方式(MDB、EJB、Web 服务、POJO 等)公开您的逻辑。它还允许您更轻松地测试业务逻辑,而无需测试协议(在本例中为 JMS)。

至于测试 JMS,模拟可能是更好的选择。或者,如果您真的需要在“容器中”测试协议,请考虑使用 JBoss Microcontainer 之类的东西(我相信您可以将它与 Seam 等一些 JBoss 项目打包在一起)。然后,您可以启动一个小型容器来测试 EJB 和 JMS 之类的东西。

但总的来说,除非绝对必要,否则最好避免使用容器。这就是为什么将您的业务逻辑与您的实现逻辑分开(即使您不使用模拟)是一个好习惯的原因。

于 2011-09-16T15:36:00.777 回答