创建 Oracle 应用开发框架项目的单元测试并在 Oracle JDeveloper 中执行这些测试的入门教程。
作者:John Stegeman
2010 年 2 月发布
下载:
除去那些非常简单的应用程序外,您会发现对应用程序的各种组件进行测试以确保其正常运行是十分重要的。随着您继续开发和修改组件,拥有在每次修改后都可以运行的测试将使您可以测试应用程序组件是否仍然正常运行。可以使用一个名为 xUnit 的通用体系结构编写这样的自动单元测试。xUnit 体系结构的一个实例名为 JUnit,它是用 Java 编写的用于开发自动单元测试的开源程序包。本文介绍如何使用 Oracle JDeveloper 11g 编写和执行用于测试 Oracle 应用开发框架 (Oracle ADF) 应用程序的 JUnit 测试。
JUnit 是一个可用于执行软件单元测试的程序,它使用一组用 Java 编写的测试用例实现这种测试。JUnit 的一般用法是创建一组单元测试,对软件进行更改时,这组单元测试可以自动运行。这样,开发人员可以确保对他们正在创建的软件所做的改动不会破坏软件以前实现的功能。JUnit 甚至还提供了一种被称作测试驱动开发 (TDD) 的开发方法,该方法主张甚至在编写某个软件之前先编写测试它的单元测试。JUnit 还提供了一个测试运行器,能够运行单元测试并报告测试是否成功。
阅读有关 JUnit 的资料时可能遇到的一些常用术语包括:
Oracle JDeveloper 11g 使用扩展提供对 JUnit 的支持。要安装扩展,执行以下步骤:
图 1 检查更新
图 2 更新中心
图 3 选择 JUnit 扩展
现在已经安装了 JUnit 扩展,您可以开始创建单元测试了。您可能会问自己的第一问题是“我为什么应该编写一个单元测试呢?”对于像本系列文章中一直使用的那些 Web 应用程序来说,这通常是一个很难回答的问题。通常,单元测试应具有以下特性。
对我们的简单应用程序(用于维护数据库中的表的 Web 应用程序)来说,要想达到前两项要求有些困难。实际上, Wikipedia 上有关测试驱动开发的文章已经承认这一点:“在需要通过完整的功能测试来确定成功或失败的情况下,使用测试驱动开发是十分困难的。这种情况的例子包括用户界面、使用数据库的程序……”
尽管 TDD 主张使用“mock”对象消除对外部资源的依赖性,但通常的折中方法是允许使用实际数据库进行单元测试。一般情况下,企业应用程序都会用到某种类型的数据库。以我编写企业应用程序的经验,这是一个非常有效的折中方法。我编写单元测试时,尝试使它们不依赖于特定的数据库状态,如果它们依赖于数据库状态,单元测试应自己创建所期望的状态。本系列稍后的一篇文章将介绍如何编写能像 JUnit 测试一样实现自动化的功能(Web 用户界面)测试。
在 Oracle ADF 应用程序中编写 JUnit 测试最好从模型层开始。我们可以编写单元测试以确保相应的应用程序模块能够生成预期的视图对象实例, 并且检查我们的数据验证规则是否正确接受了有效数据并拒绝了非法数据。
使单元测试独立于其他应用程序代码,这样便于创建不包括对非部署所必需的单元测试的部署构件(如 EAR 文件),因此通常创建一个单独的项目来存放单元测试。由于我们将为 Model(业务组件)项目创建单元测试,因此我们创建一个名为“ModelTests”的新项目来存放单元测试。在 Oracle JDeveloper IDE 中打开了 otnapp 应用程序,从 Application Navigator 快捷菜单中选择 New Project:
图 4 创建新项目
在 New Gallery 对话框中保留对 Generic Project 的选中,然后单击 OK:
图 5 选择 Generic Project
为该项目输入一个适当的名称(如“ModelTests”),然后单击 Finish:
图 6 为测试项目命名
现在我们有了一个项目,可以使用 Business Components Test Suite Wizard 为我们的 Model 项目的 Oracle ADF Business Components 建立一组基本的 JUnit 测试,还可以建立一个 JUnit 测试固件(用于建立数据库连接)和一个 JUnit 测试套件(用于运行生成的测试)。为此,右键单击 ModelTests 项目,然后从上下文菜单中选择 New...:
图 7 从上下文菜单中调用 New
出现 New Gallery 对话框后,选择 Unit Tests 类别和 Business Components Test Suite 项,然后单击 OK:
图 8 调用 Business Components Test Suite Wizard
在 JUnit ADF Business Components Test Suite Wizard 的第一个页面中,确保选中了 Business Components Project (Model.jpr),并且还选中了适当的 Application Module (OTNAppModule) 和 Configuration (OTNAppModuleLocal),然后单击 Finish:
图 9 JUnit ADF Business Components Test Suite Wizard
完成该向导后,Oracle JDeveloper 将在 ModelTests 项目中创建以下文件:
文件 | 用途 |
AllOTNAppModuleTests.java | 测试套件类 |
OTNAppModuleAMFixture.java | 测试固件类,所有测试都使用它来获得应用程序模块的实例,这样每个测试就不必创建自己的应用程序模块实例了(为了性能原因) |
DepartmentsVOTest.java、DepartmentsVOTest.xml | DepartmentsVO 视图对象的单元测试类 |
EmployeesForDepartmentVOTest.java、EmployeesForDepartmentVOTest.xml | EmployeesForDepartmentVO 视图对象的单元测试类 |
在查看代码之前,我们先运行生成的测试用例,看看会发生什么。JUnit 使用测试套件的概念将测试集合在一起,因此我们使用 Oracle JDeveloper 的 JUnit 测试运行器来运行生成的测试套件。为此,在 Application Navigator 中右键单击测试套件类 (AllOTNAppModuleTests.java),然后从上下文菜单中选择 Run:
图 10 运行测试套件
测试一旦运行,您将能够在 JUnit Test Runner – Log 窗口中看到结果:
图 11 测试套件的运行结果
从这个示例,您可以看到运行了两个单元测试并且都成功了(无失败、无错误)。现在,我们看一下生成的单元测试的实际代码并添加一些我们自己的测试。在 Application Navigator 中双击 DepartmentsVOTest.java 打开 DepartmentsVO 视图对象的单元测试的源代码:
图 12 DepartmentsVO 单元测试的源代码
您首先会注意到代码中使用了 Java 5 批注;这些批注用于向 JUnit 指示该类中的哪些方法是单元测试(带有 @Test 批注的),哪些方法是在类中每个单元测试之前 (@Before) 或之后 (@After) 要运行的。与以前的 JUnit 版本不同,@Test、@Before 或 @After 方法都不必遵守特定的命名规则。您还会注意到,JUnit 不保证 @Before 方法、@Test 方法或 @After 方法的调用顺序;只保证在每个 @Test 方法之前先调用所有 @Before 方法,在每个 @Test 方法之后再调用所有 @After 方法。
Oracle JDeveloper 生成的测试类包含一个名为 testAccess 的测试方法,该方法尝试从应用程序模块获取一个视图对象实例(该应用程序模块由测试固件创建 — 稍后会详细介绍测试固件),并使用 JUnit 的 断言 来确保获取的视图对象非空。这是在 JUnit 中进行单元测试的常见模式:执行一个测试,然后使用一个 assert 方法验证结果。如果断言不为真,则 JUnit 将这个特定的单元测试标记为失败。
正如您所看到的,向导生成的基本单元测试过于简单。现在我们针对这个视图对象编写自己的单元测试,测试是否按要求强制了 Department ID 属性。我们可以编写这样一个单元测试:创建一行,不设置 Department ID,然后对该行进行验证,看到引发了预期的异常。但是,我们如何告诉 JUnit 我们期望看到异常呢(而且如果未引发异常,测试应该失败)?通过使用 @Test 批注的 expected 属性告诉 JUnit 我们期望看到某个异常,可以达到上述目的。我们希望在未提供所需属性的情况下,Oracle ADF 应该引发一个 oracle.jbo.AttrValException,因此,我们可以按如下所示编写我们的单元测试:
图 13 用于确保必须提供 Department ID 的单元测试
如果您再次运行该测试套件类,您应该看到新加的测试成功运行:
图 14 运行新加的单元测试
到目前为止,我们的所有测试均已成功。现在,我们根据刚刚来自用户社区的新需求进行一点 TDD:部门名称至少必须 4 个字符长。本着 TDD 的精神,在编写实现这一需求的一行代码之前,我们必须先编写对该需求的测试并看到测试失败。我们可以针对此需求编写一个单元测试:尝试将部门名称设置为三个字符的字符串(有预期的异常),运行该测试套件。下面是这个测试方法:
图 15 针对最少 4 个字符的部门名称的单元测试
下面是运行该测试套件时发生的情况:
图 16 对新需求的测试失败
如果单击失败的测试,您甚至可以看到测试失败的原因:
图 17 测试失败的原因
现在我们已经完成了 TDD 的第一步,即针对新需求编写单元测试并看到该测试失败(因为我们还没有实现新需求)。下一步是实现新需求。(我在此不介绍具体步骤;简单说就是向 Departments 实体对象添加一个长度验证规则来强制四个字符的最小长度。)完成该操作后,我们可以再次运行测试套件,会看到所有测试均获成功:
图 18 所有测试再次成功
从早期版本的 JUnit 至今,测试固件和测试套件的概念已经有了一些变化,但基本思想仍然保持未变。测试固件旨在用于对一组相关测试执行任何代价高昂的设置或初始化。ADF Business Component JUnit Wizard 为您生成一个测试固件,这个测试固件执行创建 Oracle ADF 应用程序模块实例(供单元测试的其余部分使用)的代价高昂的操作。
测试套件只是可以作为一个组一起运行的测试类的逻辑分组。ADF Business Component JUnit Wizard 为您生成一个测试套件,该测试套件对固件进行初始化并通过 @Suite.SuiteClasses 批注来指定由哪些测试类组成该套件:
图 19 测试套件中的 SuiteClasses 批注
随着您向项目中添加多个测试类,您只需更新该测试套件的 @Suite.SuiteClasses 备注使其包含新创建的类。只需手动创建一个新类并添加批注,或者使用 Oracle JDeveloper JUnit Test Suite Wizard 创建类,您就可以创建更多测试套件。要调用该向导,您可以在 Application Navigator 中右键单击您的测试项目,然后单击 New...,在出现的 New Gallery 对话框中选择 Unit Tests 类别和 Test Suite 项(您可能需要选择 All Technologies 选项卡才能看到 Unit Tests 类别):
图 20 创建新测试套件
通过创建更多测试套件,您可以将单元测试分成若干逻辑组,这样可以使每次只运行单元测试的一个子集。
为了在 TDD 方法中充分利用 JUnit,应该尽可能经常运行您的单元测试。一个常用方法是使用持续集成 (CI) 服务器,该服务器在每次向版本控制系统提交代码时均构建您的代码并运行单元测试。本系列的后续文章将介绍如何建立这样的持续集成流程。将 JUnit 测试与这样的 CI 服务器相集成的常用方法是向构建过程添加 Ant 目标以便执行测试;然后对 CI 服务器进行设置,使其将单元测试作为构建过程的一部分进行运行(如果任何 JUnit 测试失败,该流程将“构建失败”)。为了实现这一目的,我们可以遵循本系列的 Ant 文章中的步骤为我们的 ModelTests 项目创建 Ant 构建文件。假设到目前为止您一直跟随本系列文章进行学习,那么简单说这些步骤包括:
图 21 JUnit 4.4 库引用
执行完这些步骤后,您的 ANT build.xml 文件应如下所示:
图 22 初始的 build.xml 文件
下一步是创建一个运行 JUnit 测试套件的 ANT 目标。幸运的是,ANT 本身包括一个运行 JUnit 测试套件和生成报告的插件。创建任务之前,我们需要导入 Model 项目的构建文件,这样我们就可以添加一个依赖性,从而确保在测试 Model 类之前先编译这些类。向 ModelTest 项目的 build.xml 文件添加下面一行代码:
图 23 导入 Model 项目的 build.xml 文件
现在可以创建一个用于执行我们的测试套件的 Ant 任务了;用于 Ant 的 JUnit 插件将生成一个测试报告,因此我们需要先向 ModelTest 项目的 build.properties 文件添加一行来指定报告的输出位置:
图 24 定义测试报告的输出目录
然后,我们更新 ModelTests.clean 和 ModelTests.init 目标以清理和创建测试报告目录:
图 25 初始化和清理测试报告目录
接下来,我们需要定义执行测试的类路径。该类路径将需要包括 ModelTest 项目的类路径、Model 项目的类路径、这两个项目的输出,以及应用程序的 .adf 和 src 目录(为了获取连接信息)。build.xml 中生成的类路径如下所示:
图 26 执行测试的类路径
最后,我们可以创建实际执行我们的测试套件的 Ant 任务。在该任务中,我们指定类路径、要运行的测试套件以及测试报告使用的格式。XML 是用于 CI 服务器的最佳格式,因此,我们这里使用这一格式:
图27 用于运行测试套件的 Ant 任务
现在,我们应该能够通过执行相应的 Ant 任务来运行我们的测试套件了。可以选择在 Oracle Jdeveloper 中运行该测试套件:
图 28 通过上下文菜单运行测试套件
也可以选择 从命令行运行该测试套件(为了正常工作,您可能需要将 junit.jar 和 ant-weblogic.jar 从 Oracle JDeveloper Ant 安装目录复制到您单独的 Ant 安装中):
图 29 从命令行运行测试套件
现在一切运行正常,不要忘记将所有更改提交到 Subversion 信息库。
现在,您已经具备了使用 JUnit 为您的 Oracle ADF 应用程序编写单元测试所需的基本知识。尽管本文主要讨论的是如何测试模型层,但您也可以轻松地为您应用程序的其他类编写单元测试。本系列的后续文章将向您介绍如何编写用于测试用户界面层的功能测试,以及如何使用 CI 服务器定期运行单元测试。
转至第 6 部分 | 返回目录
John Stegeman (http://stegemanoracle.wordpress.com) 是 Cambridge Solutions(一家跨国 BPO 和 IT 服务公司)的 EMEA 地区的 Oracle ACE 总监(Oracle 融合中间件)兼首席架构师。他从 1990 年开始就使用 Oracle 产品工作,从版本 3 开始就使用 Oracle JDeveloper 了。