// 
//     Licensed to the Apache Software Foundation (ASF) under one
//     or more contributor license agreements.  See the NOTICE file
//     distributed with this work for additional information
//     regarding copyright ownership.  The ASF licenses this file
//     to you under the Apache License, Version 2.0 (the
//     "License"); you may not use this file except in compliance
//     with the License.  You may obtain a copy of the License at
// 
//       http://www.apache.org/licenses/LICENSE-2.0
// 
//     Unless required by applicable law or agreed to in writing,
//     software distributed under the License is distributed on an
//     "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//     KIND, either express or implied.  See the License for the
//     specific language governing permissions and limitations
//     under the License.
//

= 使用嵌入的 EJB 容器测试企业应用程序
:jbake-type: tutorial
:jbake-tags: tutorials 
:jbake-status: published
:icons: font
:syntax: true
:source-highlighter: pygments
:toc: left
:toc-title:
:description: 使用嵌入的 EJB 容器测试企业应用程序 - Apache NetBeans
:keywords: Apache NetBeans, Tutorials, 使用嵌入的 EJB 容器测试企业应用程序

本教程说明了如何为 Java EE 企业应用程序创建和运行 JUnit 测试。在本教程中，将创建一个包含实体类和会话 Bean 的 Web 应用程序。首先，将为会话 Bean 创建一个 JUnit 测试类，并在嵌入的 EJB 容器中运行该测试。然后，在项目中添加一个实体类，并修改测试类以便为该实体类添加一个测试方法。

*教程练习*

* <<Exercise_1,测试会话 Bean>>
* <<Exercise_1a,创建项目>>
* <<Exercise_1b,创建会话 Bean>>
* <<Exercise_1c,测试会话 Bean>>
* <<Exercise_1d,修改测试以指定容器属性>>
* <<Exercise_1e,使用 @BeforeClass 和 @AfterClass 标注>>
* <<Exercise_2,测试实体类>>
* <<Exercise_2a,创建实体类>>
* <<Exercise_2b,修改会话 Bean>>
* <<Exercise_2c,测试实体类>>
* <<Exercise_3,下载解决方案项目>>

*要学习本教程，您需要具备以下软件和资源。*

|===
|软件或资源 |要求的版本 

|link:/downloads/[+NetBeans IDE+] |7.2、7.3、7.4、8.0、Java EE 包 

|link:http://www.oracle.com/technetwork/java/javase/downloads/index.html[+Java 开发工具包 (JDK)+] |版本 7 或 8 

|GlassFish Server Open Source Edition |3.1.x 或 4.x 
|===

NOTE: 本教程需要使用 JUnit 插件。如果在安装 IDE 时未安装 JUnit 插件，请打开 "Plugins"（插件）管理器，选择 "Available plugins"（可用插件）标签，然后安装 JUnit 插件。

*先决条件*

本文档假定您具备以下技术的一些基本知识或编程经验：

* Java 编程
* NetBeans IDE

在开始本教程之前，您可能需要熟悉以下文档。

* link:javaee-gettingstarted.html[+Java EE 应用程序入门指南+]
* link:../java/junit-intro.html[+在 NetBeans IDE 中编写 JUnit 测试+]
* link:http://download.oracle.com/docs/cd/E19798-01/821-1754/gjlde/index.html[+将 EJB 3.1 可嵌入 API 与嵌入的 GlassFish Server 一起使用+]

您可以下载link:https://netbeans.org/projects/samples/downloads/download/Samples%252FJavaEE%252FWebAppJUnit.zip[+已完成项目的 zip 档案文件+]。


== 测试会话 Bean

在本节中，将创建一个包含会话 Bean 和实体类的简单 Java EE Web 应用程序。


=== 创建项目

1. 从主菜单中选择 "File"（文件）> "New Project"（新建项目）（Ctrl-Shift-N 组合键；在 Mac 上为 ⌘-Shift-N 组合键）。
2. 从 "Java Web" 类别中选择 "Web Application"（Web 应用程序）。单击 "Next"（下一步）。
3. 将项目命名为 *WebAppJUnit* 并设置项目位置。
4. 取消选中 "Use Dedicated Folder"（使用专用文件夹）选项（如果该选项处于选中状态）。
单击 "Next"（下一步）。

[start=5]
. 将服务器设置为 *GlassFish Server*，并将 Java EE 版本设置为 *Java EE 6 Web* 或 *Java EE 7 Web*。
单击 "Finish"（完成）。


=== 创建会话 Bean

在本练习中，将创建一个非常简单的会话 Bean，它包含一个添加两个数字的方法。

1. 在 "Projects"（项目）窗口中右键单击 "WebAppJUnit" 项目，然后选择 "New"（新建）> "Other"（其他）。
2. 选择 "Enterprise JavaBeans" 类别中的 "Session Bean"（会话 Bean）。单击 "Next"（下一步）。
3. 键入 *MyBean* 作为 EJB 名称。
4. 键入 *bean* 作为包名。
5. 选择 "Stateless"（无状态）作为会话类型。单击 "Finish"（完成）。

单击 "Finish"（完成），此时将在编辑器中打开新类。


[start=6]
. 在编辑器中，将以下  ``addNumbers``  方法添加到类中。

[source,java]
----

@Stateless
public class MyBean {
    *public int addNumbers(int numberA, int numberB) {
        return numberA + numberB;
    }*
}
----

NOTE: 在本教程中，无需添加  ``@LocalBean``  标注或实现接口。默认情况下，当未显式指定视图时，Bean 将公开无界面视图。


[start=7]
. 保存所做的更改。


=== 测试会话 Bean

在本练习中，将为会话 Bean 创建一个测试类以测试  ``addNumbers``  方法。IDE 可以根据目标类中的方法，生成新的测试类和框架测试方法。

1. 在 "Projects"（项目）窗口中右键单击  ``MyBean``  类，然后选择 "Tools"（工具）> "Create Tests"（创建测试）。
2. 在 "Frameworks"（框架）下拉列表中选择 "JUnit"。
3. 在 "Create Tests"（创建测试）对话框中，使用默认值。单击 "OK"（确定）。

image::images/create-tests-dialog.png[title=""Create Tests"（创建测试）对话框"]

NOTE: 首次创建 JUnit 单元测试时，您需要指定 JUnit 版本。在 "Select JUnit Version"（选择 JUnit 版本）对话框中，选择 "JUnit 4.x"，然后单击 "Select"（选择）。

单击 "OK"（确定）后，IDE 将生成  ``MyBeanTest.java``  文件并在编辑器中打开该类。

在 "Projects"（项目）窗口中，您可以看到 IDE 在 "Test Packages"（测试包）节点下面生成了测试类。默认情况下，IDE 在测试类中生成一个框架测试方法，它通过调用  ``javax.ejb.embeddable.EJBContainer.createEJBContainer()``  来创建 EJB 容器实例。 ``createEJBContainer()``  方法是 EJB 3.1 可嵌入 API 中包含的 link:http://download.oracle.com/javaee/6/api/javax/ejb/embeddable/EJBContainer.html[+ ``EJBContainer`` +] 类中的方法之一。

如果在 "Projects"（项目）窗口中展开 "Test Libraries"（测试库）节点，您可以看到 IDE 自动添加了 GlassFish Server（可嵌入容器）和 JUnit 4.x 作为测试库。如果展开 GlassFish Server 库，则可以看到该库包含  ``glassfish-embedded-static-shell.jar`` 。

image::images/embedded-static-shell-jar.png[title=""Projects"（项目）窗口中的项目结构"]

NOTE:  ``glassfish-embedded-static-shell.jar``  JAR 不包含嵌入的 EJB 容器的源代码。 ``glassfish-embedded-static-shell.jar``  JAR 要求在本地安装 GlassFish。本地 GlassFish 安装的类路径是由项目的目标服务器确定的。您可以在项目的 "Properties"（属性）对话框中更改目标服务器。


[start=4]
. 修改生成的框架测试方法以指定  ``numberA`` 、 ``numberB``  和  ``expResult``  的值，然后删除会失败的默认调用。

[source,java]
----

@Test
public void testAddNumbers() throws Exception {
    System.out.println("addNumbers");
    *int numberA = 1;
    int numberB = 2;*
    EJBContainer container = javax.ejb.embeddable.EJBContainer.createEJBContainer();
    MyBean instance = (MyBean)container.getContext().lookup("java:global/classes/MyBean");
    *int expResult = 3;*
    int result = instance.addNumbers(numberA, numberB);
    assertEquals(expResult, result);
    container.close();
}
----

[start=5]
. 在 "Projects"（项目）窗口中右键单击项目，然后选择 "Test"（测试）。

运行测试时，将在 IDE 中打开 "Test Results"（测试结果）窗口并显示测试进度和结果。

image::images/test-results1.png[title=""Test Results"（测试结果）窗口"]

将在 "Output"（输出）窗口中看到类似以下的内容。


[source,java]
----

Testsuite: bean.MyBeanTest
addNumbers
...
Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 31.272 sec

------------- Standard Output ---------------
addNumbers
...
------------- ---------------- ---------------
test-report:
test:
BUILD SUCCESSFUL (total time: 35 seconds)
----


=== 修改测试以指定容器属性

使用创建测试向导时，IDE 生成一个默认框架测试类，它包含用于启动 EJB 容器的代码。在本练习中，将修改用于启动该容器的生成代码，以便为嵌入的容器实例指定其他属性。

1. 将以下代码（粗体）添加到测试类中。

[source,java]
----

@Test
public void testAddNumbers() throws Exception {
    System.out.println("addNumbers");
    int numberA = 1;
    int numberB = 2;

    // Create a properties map to pass to the embeddable container:
    *Map<String, Object> properties = new HashMap<String, Object>();*
    // Use the MODULES property to specify the set of modules to be initialized,
    // in this case a java.io.File 
    *properties.put(EJBContainer.MODULES, new File("build/jar"));*

    // Create the container instance, passing it the properties map:
    EJBContainer container = javax.ejb.embeddable.EJBContainer.createEJBContainer(*properties*);

    // Create the instance using the container context to look up the bean 
    // in the directory that contains the built classes
    MyBean instance = (MyBean) container.getContext().lookup("java:global/classes/MyBean");

    int expResult = 3;

    // Invoke the addNumbers method on the bean instance:
    int result = instance.addNumbers(numberA, numberB);

    assertEquals(expResult, result);

    // Close the embeddable container:
    container.close();
}
----

[start=2]
. 在编辑器中右键单击，然后选择 "Fix Imports"（修复导入）（Alt-Shift-I 组合键；在 Mac 上为 ⌘-Shift-I 组合键）添加  ``java.util.HashMap``  和  ``java.util.Map``  的 import 语句。

[start=3]
. 再次运行测试，以确认修改的测试正常工作并且正确创建了容器。

您可以在 "Test Results"（测试结果）窗口中单击 "Rerun"（重新运行）按钮。

 


=== 使用  ``@BeforeClass``  和  ``@AfterClass``  标注

在本练习中，将修改创建单个方法所需的测试类，以便创建和关闭容器实例。如果要运行几个可使用相同容器实例的测试，这可能是非常有用的。这样，您就不需要针对每个测试打开和关闭容器实例，只需在运行测试之前创建一个实例，并在完成所有测试后关闭该实例。

在本练习中，您需要将用于创建 EJB 容器的代码移到  ``setUpClass``  方法中。 ``setUpClass``  方法是使用  ``@BeforeClass``  标注的，用于指示在测试类中的其他方法运行之前将要运行的某个方法。在本示例中，将在  ``testAddNumbers``  测试方法之前创建容器实例，并且该容器在关闭之前将一直存在。

同样，您需要将用于关闭该容器的代码移到  ``tearDownClass``  方法中，该方法是使用  ``@AfterClass``  标注的。

1. 将以下字段添加到测试类中。

[source,java]
----

private static EJBContainer container;
----

[start=2]
. 将用于创建容器的代码从  ``testAddNumbers``  测试方法复制到  ``setUpClass``  方法和

[source,java]
----

@BeforeClass
public static void setUpClass() *throws Exception* {
    *Map<String, Object> properties = new HashMap<String, Object>();
    properties.put(EJBContainer.MODULES, new File("build/jar"));
    container = EJBContainer.createEJBContainer(properties);
    System.out.println("Opening the container");*
}
----

[start=3]
. 将用于关闭容器的代码从  ``testAddNumbers``  测试方法复制到  ``tearDownClass``  方法中。

[source,java]
----

@AfterClass
public static void tearDownClass() *throws Exception* {
    *container.close();
    System.out.println("Closing the container");*
}
----

[start=4]
. 从  ``testAddNumbers``  方法中删除多余的代码。保存所做的更改。

现在，测试类应如下所示。


[source,java]
----

public class MyBeanTest {
    private static EJBContainer container;

    public MyBeanTest() {
    }

    @BeforeClass
    public static void setUpClass() throws Exception {
        Map<String, Object> properties = new HashMap<String, Object>();
        properties.put(EJBContainer.MODULES, new File("build/jar"));
        container = EJBContainer.createEJBContainer(properties);
        System.out.println("Opening the container");
    }

    @AfterClass
    public static void tearDownClass() throws Exception {
        container.close();
        System.out.println("Closing the container");
    }

    @Before
    public void setUp() {
    }

    @After
    public void tearDown() {
    }

    /**
     * Test of addNumbers method, of class MyBean.
     */ 
    @Test
    public void testAddNumbers() throws Exception {
        System.out.println("addNumbers");
        int numberA = 1;
        int numberB = 2;

        // Create the instance using the container context to look up the bean 
        // in the directory that contains the built classes
        MyBean instance = (MyBean) container.getContext().lookup("java:global/classes/MyBean");

        int expResult = 3;

        // Invoke the addNumbers method on the bean instance:
        int result = instance.addNumbers(numberA, numberB);

        assertEquals(expResult, result);
    }
}
----

如果再次运行测试以确认正确创建和关闭了容器，则 "Test Results"（测试结果）窗口中将会显示类似下面的输出。

image::images/test-results2a.png[title=""Test Results"（测试结果）窗口"]

您可以看到在  ``addNumbers``  测试之前运行了  ``setUpClass``  方法并输出了 "Opening the container"。


== 测试实体类

在本节中，将创建一个实体类和持久性单元，并修改会话 Bean 以注入实体管理器和访问实体。在新实体类中，将添加一个简单方法以输出条目的 ID 号。然后，在会话 Bean 中添加一些简单方法以在数据库中创建和验证条目。


=== 创建实体类

在本节中，将通过新建实体类向导使用数据库连接详细信息创建一个实体类和持久性单元。

1. 在 "Projects"（项目）窗口中右键单击 "WebAppJUnit" 项目，然后选择 "New"（新建）> "Other"（其他）。
2. 在 "Persistence"（持久性）类别中选择 "Entity Class"（实体类）。单击 "Next"（下一步）。
3. 在 "Class Name"（类名）中键入 *SimpleEntity*。
4. 从 "Package"（包）下拉列表中选择 "Bean"。
5. 在 "Primary Key Type"（主键类型）中键入 *int*。单击 "Next"（下一步）。
6. 使用默认的持久性单元名称和持久性提供器。
7. 选择  ``jdbc/sample``  作为数据源，然后选 "Drop and Create"（删除并创建）作为策略。单击 "Finish"（完成）。

image::images/create-entity-wizard.png[title=""Create Entity Class"（创建实体类）对话框"]

单击 "Finish"（完成），此时将在编辑器中打开新的实体类。如果在 "Projects"（项目）窗口中展开 "Configuration Files"（配置文件）节点，则可以看到 IDE 自动生成了  ``persistence.xml``  文件，该文件定义了  ``WebAppJUnitPU``  持久性单元的属性。


[start=8]
. 在编辑器中，将以下私有字段添加到实体类中。

[source,java]
----

private String name;
----

[start=9]
. 在源代码编辑器中右键单击，选择 "Insert Code"（插入代码）（Alt-Insert 组合键；在 Mac 上为 Ctrl-I 组合键），然后选择 "Getter and Setter"（Getter 和 Setter），以打开 "Generate Getters and Setters"（生成 Getter 和 Setter）对话框。

[start=10]
. 在对话框中选择  ``name``  字段。单击 "Generate"（生成）。

[start=11]
. 将以下方法添加到类中。

[source,java]
----

public SimpleEntity(int id) {
    this.id = id;
    name = "Entity number " + id + " created at " + new Date();
}
----

[start=12]
. 使用  ``@NamedQueries``  和  ``@NamedQuery``  标注来创建指定的 SQL 查询。

[source,java]
----

@Entity
*@NamedQueries({@NamedQuery(name = "SimpleEntity.findAll", query = "select e from SimpleEntity e")})*
public class SimpleEntity implements Serializable {
----

[start=13]
. 创建一个默认构造函数。

如果希望 IDE 生成构造函数，您可以单击类声明旁边的装订线中显示的建议图标。


[start=14]
. 修复导入以添加  ``javax.persistence.NamedQueries`` 、 ``javax.persistence.NamedQuery``  和  ``java.util.Date``  的 import 语句。保存所做的更改。

除了默认的生成代码以外，实体类现在应类似于以下内容：


[source,java]
----

package bean;

import java.io.Serializable;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;


@Entity
@NamedQueries({@NamedQuery(name = "SimpleEntity.findAll", query = "select e from SimpleEntity e")})
public class SimpleEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

    private String name;

    public SimpleEntity() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public SimpleEntity(int id) {
        this.id = id;
        name = "Entity number " + id + " created at " + new Date();
    }

    

    ...

}
----


=== 修改会话 Bean

在本练习中，将编辑  ``MyBean``  会话 Bean 以添加在数据库表中插入和检索数据的方法。

1. 在编辑器中打开  ``MyBean.java`` 。
2. 在编辑器中右键单击，选择 "Insert Code"（插入代码）（Alt-Insert 组合键；在 Mac 上为 Ctrl-I 组合键），然后从弹出式菜单中选择 "Use Entity Manager"（使用实体管理器）。

选择 "Use Entity Manager"（使用实体管理器）时，IDE 将在类中添加以下代码以注入实体管理器。您可以看到自动生成了持久性单元的名称。


[source,java]
----

@PersistenceContext(unitName="WebAppJUnitPU")
private EntityManager em;
----

[start=3]
. 添加以下  ``verify``  和  ``insert``  方法。

[source,java]
----

@PermitAll
public int verify() {
    String result = null;
    Query q = em.createNamedQuery("SimpleEntity.findAll");
    Collection entities = q.getResultList();
    int s = entities.size();
    for (Object o : entities) {
        SimpleEntity se = (SimpleEntity)o;
        System.out.println("Found: " + se.getName());
    }

    return s;
}

@PermitAll
public void insert(int num) {
    for (int i = 1; i <= num; i++) {
        System.out.println("Inserting # " + i);
        SimpleEntity e = new SimpleEntity(i);
        em.persist(e);
    }
}
----

[start=4]
. 修复导入以导入  ``javax.persistence.Query``  并保存所做的更改。


=== 测试实体类

在本练习中，将编辑测试类以添加一个方法，测试应用程序是否可以查找 EJB 以及  ``insert``  和  ``verify``  方法是否正常工作。

1. 启动 JavaDB 数据库。
2. 在编辑器中打开  ``MyBeanTest.java``  测试类。
3. 编辑该测试类以添加以下  ``testInsert``  测试方法。

[source,java]
----

@Test
public void testInsert() throws Exception {

    // Lookup the EJB
    System.out.println("Looking up EJB...");
    MyBean instance = (MyBean) container.getContext().lookup("java:global/classes/MyBean");

    System.out.println("Inserting entities...");
    instance.insert(5);
    int res = instance.verify();
    System.out.println("JPA call returned: " + res);
    System.out.println("Done calling EJB");

    Assert.assertTrue("Unexpected number of entities", (res == 5));
    System.out.println("..........SUCCESSFULLY finished embedded test");
}
----

[start=4]
. 在 "Projects"（项目）窗口中右键单击项目节点，然后从弹出式菜单中选择 "Test"（测试）。

此时将打开 "Test Results"（测试结果）窗口并显示类似下面的输出。

image::images/test-results2b.png[title="添加 testInsert 测试后的 "Test Results"（测试结果）窗口"]

您可以通过添加到测试类中的输出消息查看测试进度和测试运行顺序。

现在您已为会话 Bean 创建了测试并知道实体类连接正常工作，因此可以开始对应用程序的 Web 接口进行编码。 


== 下载解决方案项目

您可以采用下列方法下载本教程的解决方案（作为一个项目）。

* 下载link:https://netbeans.org/projects/samples/downloads/download/Samples%252FJavaEE%252FWebAppJUnit.zip[+已完成项目的 zip 档案文件+]。
* 通过执行以下步骤从 NetBeans 样例检出项目源代码：
1. 从主菜单中选择 "Team"（团队开发）> "Subversion" > "Checkout"（检出）。
2. 在 "Checkout"（检出）对话框中，输入以下资源库 URL：
 ``https://svn.netbeans.org/svn/samples~samples-source-code`` 
单击 "Next"（下一步）。

[start=3]
. 单击 "Browse"（浏览）以打开 "Browse Repository Folders"（浏览资源库文件夹）对话框。

[start=4]
. 展开根节点并选择 *samples/javaee/WebAppJUnit*。单击 "OK"（确定）。

[start=5]
. 指定用于存储源代码的本地文件夹（本地文件夹必须为空）。

[start=6]
. 单击 "Finish"（完成）。

单击 "Finish"（完成），此时 IDE 会将本地文件夹初始化为 Subversion 资源库，并检出项目源代码。


[start=7]
. 在完成检出操作后将会显示一个对话框，在该对话框中单击 "Open Project"（打开项目）。

NOTE: 需要 Subversion 客户端检出源代码。有关安装 Subversion 的更多信息，请参见 link:../ide/subversion.html[+NetBeans IDE 中的 Subversion 指南+]中有关link:../ide/subversion.html#settingUp[+设置 Subversion+] 的部分。


link:/about/contact_form.html?to=3&subject=Feedback:%20Using%20the%20Embedded%20EJB%20Container[+发送有关此教程的反馈意见+]



== 另请参见

有关使用 NetBeans IDE 开发 Java EE 应用程序的更多信息，请参见以下资源：

* link:javaee-intro.html[+Java EE 技术简介+]
* link:javaee-gettingstarted.html[+Java EE 应用程序入门指南+]
* link:../web/quickstart-webapps.html[+Web 应用程序开发简介+]
* link:../../trails/java-ee.html[+Java EE 和 Java Web 学习资源+]

您可以在 link:http://download.oracle.com/javaee/6/tutorial/doc/[+Java EE 6 教程+]中找到有关使用 EJB 3.1 企业 Bean 的详细信息。

要发送意见和建议、获得支持以及随时了解 NetBeans IDE Java EE 开发功能的最新开发情况，请link:../../../community/lists/top.html[+加入 nbj2ee 邮件列表+]。

