级别: 中级 David J.N. Artus (artusd@uk.ibm.com), IT 咨询专家, IBM
2006 年 12 月 20 日 可重复的单元测试为验证解决方案组件的质量提供了一种有效而可靠的方法。本文描述在使用业务流程执行语言 (BPEL) 测试实现业务流程的 Service Component Architecture (SCA) 模块时可能遇到的问题,以及如何使用模拟对象实现对这些组件的可重复测试。
摘自 IBM WebSphere 开发者技术期刊。
引言
在该组件测试系列的第 1 部分中,我们介绍了一种方法,该方法演示了如何使用 JUnit 开放源代码框架和 Cactus(其服务器端扩展)对 Service Component Architecture (SCA) 组件执行可重复的测试,而且我们还介绍了如何在一组具有可选有效负载文件的测试规范文件中定义组件的单元测试套件。图 1 显示了服务的主要测试规范部分,该服务采用一个简单的 PostCode 字符串,并返回一个数据对象,预期的结果在一个简单的有效负载文件中指定。
图 1. 示例测试规范
由于 Eclipse JUnit 执行工具是随 WebSphere Integration Developer 提供的,因此运行一组测试非常简单。而且测试的结果可以显示出来,如图 2 所示。
图 2. JUnit 测试执行结果
在本文中,我们将描述在使用业务流程执行语言 (BPEL) 测试实现业务流程的 SCA 组件时可能遇到的一些问题,然后讨论如何使用模拟对象解决这些问题。我们还提供了一些快速创建模拟对象的技术。本文附带了一个下载文件,其中包含实现这些技术的代码示例。
如果您计划学习本文(即该系列文章的第二篇)中提供的示例,则首先学习上一篇文章可为您提供一些帮助。
测试 BPEL
由于 WebSphere Process Server 编程模型将业务流程公开为 SCA 组件,可以使用与任何其他 SCA 组件相同的方式调用该组件,因此我们预期可以使用与任何其他组件相同的方式测试 BPEL SCA 组件。不过,我们发现使用这样的方法存在一些问题。请参见图 3 中显示的一个非常简单的示例流程。
图 3. 简单的 BPEL 流程
此示例演示了三个主要疑难问题:
- 业务流程使用一个人工任务,而且人工任务的结果控制一些关键的执行途径。
- 外部服务在单元测试环境中不能随时执行,例如,它们可能依赖于开发人员无法使用的基础结构。
- 业务流程虽然启动,但没有对应的应答。
现在让我们详细地了解一下这些问题。
1. 人工任务
在现实世界的业务流程实现中使用人工任务非常普遍。显然,如果我们要实现一组可以自动执行的可重复测试,则必须想办法省去人工干预的必要。而且,我们的示例显示了一个非常普遍的模式:人工任务的输出结果确定采用哪个代码路径。在图 3 中,可以看到对 InvokeAgreedPayment 和 InvokeCaseClosed 的选择取决于人工响应。
如果我们的测试是为了实现较好的代码覆盖,那么必须能够控制人工任务的结果。因此,产生了下面的需求:
必须能够模拟人工操作对不同输入数据进行的响应。
2. 外部服务
外部服务除了先前提到的单向响应问题外,还存在许多难题:
- 在单元测试环境中,有些服务(例如,那些使用大型机或 ERP 系统实现的服务)可能对开发人员不可用。
- 即使在这些系统可用时,测试数据也可能不适于使用所有代码路径;也就是说,模拟一些错误条件可能会很困难。
- 在许多情况下,服务的开发与业务流程的开发是并行发生的。因此,不能保证在需要进行单元测试时能够提供服务实现。
这些因素导致另一个测试需求:
必须能够模拟服务操作对不同输入数据进行的响应。
3. 单向调用
到目前为止,我们所有的 SCA 测试都具有这样的模式:调用服务,验证响应。我们的示例业务流程与许多现实世界的业务流程一样,都没有响应。我们可以随时构建一个单元测试来调用 BPEL 流程,但不能响应验证,那么如何才能确定它的成功或失败呢?确定成功或失败必须依赖于对操作(如调用其他服务)是否正确执行的确定能力。
如果我们的示例流程正确实现,则输入数据的特定值将导致服务调用 InvokeAutoSettlement,而其他输入值将导致人工任务的创建,并进而导致其他服务的调用。
这就引出如下测试需求:
必须能够确定已经发生了服务调用。
我们还注意到,在两个服务调用之前存在分配语句,该语句可能非常复杂。这就引出了另一个需求:
必须能够确定在调用服务时使用了正确的数据。
还有另外一个需求,该需求在 BPEL 流程具有比较复杂的逻辑时特别重要。很明显,在我们的简单示例中,如果使用了 InvokeAutoSettlement,就不会使用 InvokeAgreedPayment。单元测试应能够确定已正确实现了此类互斥性。因此:
必须能够确定没有调用服务。
在考虑如何满足此类测试需求之前,让我们先考虑一些其他问题。
模拟对象和模拟对象控制器
我们已经建立了模拟 SCA 组件的需求(如人工任务和外部服务),以便它们可以在 BPEL 流程中驱动特定的代码路径。这通过名为模拟对象的常见测试模式实现。
在 WebSphere Integration Developer 中实现此类模拟对象服务相当简单。我们的起点是一个实际组件接口,如图 4 所示。
图 4. Customer 接口
使用 WebSphere Integration Developer,通过单击几下鼠标就可以新建一个实现该接口的 SCA 组件,如图 5 所示。
图 5. 模拟 Customer
再单击几下鼠标,我们可以生成该接口的框架实现:
public class MockCustomerImpl {
// ... constructor etc. removed ...
public DataObject getCustomer(Integer customerId)
throws ServiceBusinessException {
//TODO Needs to be implemented.
return null;
}
} |
然后我们可以通过向结构添加代码、进行填充并返回一个适当的数据对象来完成功能性模拟对象的创建。原则上,创建此类代码不是特别困难,但可能让人感到有些枯燥无味,而且在编写时也容易出错,特别是在数据对象比较大并包含嵌入的对象和数组时。该问题与我们在上一篇文章中执行测试调用时遇到的问题非常类似。当时,我们需要构建特别复杂的有效负载对象,我们的解决方案是将有效负载数据外化到 XML 文件。这里,我们将介绍如何对模拟对象的返回值应用相同的思想,将返回值外化到 XML 文件。
使用模拟对象,我们可以替代人工任务和服务组件,对 BPEL 流程创建可重复利用的测试;我们将能够在无人工干预和不依赖于外部系统的情况下执行流程。
因此,我们已经解决了一些测试需求:可以实际执行测试了。需要考虑的其余问题是验证测试是否成功。要验证测试是否成功,我们需要验证:
- 是否使用正确的数据调用了特定的服务。
- 是否没有调用特定的服务。
我们通过扩展模拟对象的功能完成了这一点,即添加了第二个接口(图 6)。
图 6. 模拟控制器接口
在此接口中,我们可以看到四个操作。方案中使用的 loadData 操作用于外化返回数据,这将在稍后的部分中讨论。其余的三项操作 resetLastRequest、checkNoRequest 和 checkExpectedResult 可以实现对服务调用的验证。
然后,我们的模拟对象将同时实现它替换的组件的服务接口和控制器接口。模拟对象实现的大致代码如以下所示:
public class MockServiceImpl {
// ... constructor etc. removed ...
private DataObject lastRequest = null
// the service method used by the BPEL process under test
public void exampleServiceMethod(DataObject requestData)
throws ServiceBusinessException {
lastRequest = requestData;
}
public void resetLastRequest(String label)
throws ServiceBusinessException {
lastRequest = null;
}
public String checkNoRequest()
throws ServiceBusinessException {
assertIsNull(lastRequest);
}
public String checkExpectedRequest(DataObject expectedValue)
throws ServiceBusinessException {
// code to compare expectedValue with lastRequest
}
} |
测试使用 exampleServiceMethod 的 BPEL 流程包括三个步骤,每个步骤都在自己的 .tspec 文件中指定:
- 调用模拟控制器 resetLastRequest。
- 使用指定值启动 BPEL 流程,将 BPEL 驱动到使用特定值调用 exampleServiceMethod 的路径。
- 调用模拟控制器 checkExpectedRequest,传递预期的值;如果找不到预期的值,则模拟控制器将发出异常,测试将失败。
类似地,可以定义第二个测试序列来验证没有调用 exampleServiceMethod:
- 调用模拟控制器 resetLastRequest。
- 使用指定值启动 BPEL 流程,将 BPEL 驱动到不调用 exampleServiceMethod 的路径。
- 调用模拟控制器 checkNoRequest;如果 lastRequest 不为空(指没有意外调用 exampleServiceMethod),则模拟控制器将发出异常,测试将失败。
我们在这里描述的方案非常简单,意在演示将模拟控制器接口与模拟对象一起使用的原则。对于以下要求较为苛刻的场景,该方案尚不足以满足需要:
- 多用户场景,其中许多模拟对象实例服务于许多请求。
- BPEL 流程连续快速地对同一模拟对象发出多个不同的调用。
此类场景表明需要使用更完善的方案,而不只是保存和检查最近的请求数据。在本文的其余部分中,我们将介绍如何实现简单的模拟对象方案,其中利用一组库和组件大大减少了所需的工作。如果您的测试场景需要更复杂的模拟控制器实现,则此处描述的结构将提供一个好的起点。
测试场景
我们的测试场景是管理长期存储项目的虚构存档应用程序的一部分。我们将使用一个业务流程,客户通过该业务流程请求检索一个项目,然后得到通知检索已经完成。
您可以下载一个包含本例中所有代码的项目互换文件。如果您想按照本文进行操作,请下载这些项目:
表 1. 要导入的项目
| 项目 | 类型 | 内容 |
|---|
| L_Archive | SCA 库 | 存档应用程序组件的接口。作为应用程序的一部分提供,用于 JUnit 测试。 | | MP_ArchiveRetrieval | SCA 模块 | 存档应用程序处理模块。包含我们要测试的 BPEL 流程。 | | J_ScaUtilities | Java 库 | 应用程序和测试都需要使用的工具。 | | LT_ScaTest | SCA 库 | 单元测试和模拟对象使用的接口,不用于应用程序。 | | LT_ScaJunitTest | SCA 库 | JUnit SCA 测试执行代码。仅供 JUnit 测试使用。 | | MT_TestArchiveRetrieval | SCA 模块 | 提供 JUnit 测试的模块 | | MT_TestArchiveRetrievalJUnitWeb | J2EE Web 项目 | JUnit 测试代码、测试定义和有效负载数据,MT_TestArchiveRetrieval 的一部分。 |
如果打开 MP_ArchiveRetrieval 模块的装配图,您会看到检索流程和它使用的服务提供的接口。
图 7. 存档模块装配图
流程接口
我们看到该流程有一个启动接口(图 8)。
图 8. 启动接口
客户可以通过适当的用户接口合理访问此接口。该流程另外还有一个接口,该接口向流程通知检索请求的完成情况(图 9)。
图 9. 检索结果通知接口
检索流程使用的服务
业务流程使用两项服务。Customer 服务将通过客户的唯一 ID 检索客户信息(图 10)。
图 10. Customer 服务接口
当检索完成之后,该流程将通过 CustomerCommunication 服务通知 Customer(图 11)。
图 11. CustomerCommunication 服务
需要的模拟对象
您导入的项目不包括 Customer 和 CustomerCommunication 服务的实现。在本文稍后的部分中我们将创建这两个服务的模拟对象。(如果您希望跳过创建模拟对象的工作,则可以从项目互换文件中导入项目 MT_MockCustomer 和 MT_MockCustomerCommunition。)
检索流程
在开始使用模拟对象之前,您可能希望检查当前实现的业务流程。请打开流程以查看整体结构,如图 12 所示。
图 12. 检索流程
在此阶段,我们需要重点关注使用红色突出显示的三个项目:
- 标记为 Initiation 的 Receive 语句:这是我们单元测试启动的目标。
- 标记为 GetCustomerDetails 的 Invoke 语句:稍后我们将对它进行详细介绍,但请注意,这是将调用模拟对象的一个点。
- 标记为 NotifyCustomer 的 Invoke 语句:这是第二个模拟对象的调用点。
您还可能注意到,这与完整的进程实现相差很远;例如,没有对存档系统的实际访问。我们提供的这个简化示例是为了便于理解;不过,它体现了两点:
- 我们倾向于以递增的方式构建和测试大型 BPEL 流程,着重关注整体流程的特定部分。如果有必要进行任何重构,可重复的测试为我们增加了信心。
- 一个重要的原则(特别是与性能测试相关)是“早测试,常测试”。我们甚至希望将一个如本例所示的框架流程引入集成测试和性能测试环境。以前的经验使我们可以立即洞察稳定性、错误处理和性能特征。
我们还可以检查用于 GetCustomerDetails 调用的错误处理程序,指明可能需要区别对待 notFound 错误和 transientError 错误(图 13)。
图 13. GetCustomerDetails 上的错误
在这个简化的代码中,我们没有对不同的错误进行特殊处理;不过,在实际的实现中,需要使用这些不同的路径。在实现模拟对象时,我们需要考虑如何生成驱动不同路径所需的不同错误。
单元测试
要检查我们要运行的单元测试,请在 WebSphere Integration Developer 中打开 Web 透视图。在 Project Explorer 视图中,展开 Dynamic Web Projects => MT_TestArchiveRetrievalJunitWeb => Java Resources => Java Source => com.ibm.issw.archive.utsuite => ArchiveTest-Data => testArchiveInitiation(图 14)。
图 14. 单元测试规范
ArchiveInitiation 测试包括五个步骤。依次打开每个 tspec 文件,以了解测试是如何执行的:
- 010-InitialiseCustomers 重置 Customer 模拟对象。
- 020-InitialiseNotification 重置 CustomerCommunication 模拟对象。
- 030-LoadCustomers 使用一些客户数据初始化 Customer 模拟对象。(稍后我们将解释它的工作原理。)
- 040-InitiateRetrieval 启动业务流程本身。预期的操作是流程将使用 GetCustomerDetails 从 Customer 模拟对象获取数据,然后使用 CustomerCommunication 模拟对象的 NotifyCustomer 功能。
- 050-CheckNotification 然后将验证是否使用正确的数据调用了 CustomerCommiunication 模拟对象。
我们还将在 CustomerTest-Data 目录中看到 CustomerTest.java 拥有的另一组测试(图 15)。这些测试可让我们直接使用 Customer 模拟对象。提供这些内容是为了能够快速演示我们接下来将要构建的模拟对象。
图 15. Customer 测试
我们将在完成构建 Customer 模拟对象后并准备运行时描述这些测试的目的。
Customer 模拟对象
要创建 Customer 模拟对象,我们需要执行下列常规步骤:
- 创建模拟 Customer 模块
- 创建模拟 Customer 组件
- 生成一个实现类
- 实现 loadData 操作
- 实现 getCustomerDetails 操作
- 测试模拟对象
A. 创建模拟 Customer 模块
在这些示例中,我们将为每个模拟对象创建单独的模块。对于实际的开发场景,这会创建相当多的模块和 WebSphere Integration Developer 对象,但是我们发现创建单独的模块可以减少复杂性。
要创建模块,请执行以下步骤:
在 Business Integration 透视图的 Business Integration 视图中,右键单击并选择 New => Module 以显示 New Module 对话框。
输入模块名称 MT_MockCustomer 并单击 Finish。
模拟对象必须从 L_Archive 库实现 Customer 接口。我们还将从测试框架中利用一些库。因此,需要设置必要的依赖项。双击新创建的项目以弹出 Dependency 编辑器。
在 Libraries 会话中单击 Add,打开 Library Selection 对话框。
选择 L_Archive 并单击 OK。
类似地,将依赖项添加到 J_ScaUtilities、L_ScaTest(图 16)。
图 16. 添加库
注意,模拟对象本身不使用 JUnit,因此我们不需要在 LT_ScaJUnitTest 库中添加依赖项。
分别使用 File => Save and File => Close 保存您的更改并关闭依赖项编辑器。
B. 创建模拟 Customer 组件
选择 MT_MockCustomer 组装图并双击以打开编辑器。
在选项栏中选择最上面的元素,并从下级选项栏中选择 Component(无实现类型),然后单击编辑图面。这会创建模拟对象组件。(图 17)
图 17. 模拟 Customer 组装图
右键单击新创建的组件。重命名组件 MockCustomer。模拟 Customer 组件的主要作用是提供 CustomerService 接口。
从上下文菜单中选择 Add Interface 以弹出 Add Interface 对话框。
选择 I_CustomerService 并单击 OK。您可以在 Properties 视图的 Details 选项卡中看到结果(图 18)。
图 18. 具有服务接口的模拟 Customer
C. 生成一个实现类
要创建模拟 Customer 组件的框架实现,请执行以下步骤:
右键单击该组件并选择 Generate Implementation,以打开 Generate Implementation 对话框。
此对话框可让您指定实现类的 Java 包,提供从接口命名空间派生的名称。通用约定是使用与您自己的命名约定相符合的包名称。要这样做,请单击 b.New Package,然后输入合适的名称。我们使用 com.ibm.issw.archive.mock.customer(图 19)。单击 OK。
图 19. 实现包
选择新创建的包并再次单击 OK 以创建实现代码(图 20)。
图 20. 生成的实现
现在,再次选择 Add Interface 并添加 I_MockServiceController 接口。注意,我们在添加第二个接口之前已经生成了服务实现;我们将使用模拟控制器接口的库实现,因此不需要为第二个接口编写任何代码。
图 21. 模拟对象接口
您可以在 Properties 视图的 Details 选项卡中看到结果(图 22)。
图 22. 模拟 Customer 接口
两个其他对象 MT_TestArchiveRetrieval 和 M_Archive 将需要使用此模拟组件,因此导出接口以提供对那些对象的访问。右键单击 MockCustomer 组件并选择 Generate Export => SCA Binding 以打开 Select Interface 对话框(图 23)。
检查这两个接口,然后单击 OK。
图 23. 导出模拟 Customer 接口
现在,组装图应如图 24 所示。
图 24. 模拟 Customer 导出
保存组装图。现在我们已经准备好提供接口的实现。
D. 实现 loadData 操作
现在我们已经将模拟 Service Controller 接口添加到 MockCustomer 组件,这时您将看到一条错误,因为我们还没有实现所需的操作(图 25)。
图 25. 实现错误
在测试框架的基础类 ScaMockBase 中提供了一组合适的实现。
按如下所示,通过添加 ScaMockBase 的导入和扩展子句来修改 MockCustomerImpl.java 实现以扩展此基础类:
import com.ibm.swservices.sca.test.ScaMockBase;
import com.ibm.websphere.sca.ServiceBusinessException;
import commonj.sdo.DataObject;
import com.ibm.websphere.sca.ServiceManager;
public class MockCustomerImpl extends ScaMockBase { |
保存这些更改。这将会消除错误。
在类名称上检查 ScaMockBase class.Click 的代码,并按 F3 打开此代码。您将看到一些受保护的(因此可以访问您的 MockCustomer 类)成员变量:
protected Logger m_logger = Logger.getLogger(this.getClass().getName());
protected Object m_lastRequest = null;
protected String m_lastFaultName = null;
protected List m_dataList = new ArrayList(); |
您将看到 ScaMockBase 使用 XML 文件中的有效客户填写 m_dataList:
DataObject data = XmlSdoUtilities.readDataFromXmlFile(dataFiles[count]);
m_dataList.add(data);
|
我们将在 getCustomerDetails 实现中使用此列表。需要创建一些包含一组有效客户数据的文件。这些文件的格式是 Customer 数据对象的序列化形式:
<?xml version="1.0" encoding="UTF-8"?>
<_:TestDefinition xsi:type="l:Customer"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:l="http://L_Archive"
xmlns:_="http://scatest/issw/ibm/com">
<customerId>2</customerId>
<name>Bingley Bradford</name>
<email>bb@bbbs.com</email>
<phone>22222</phone>
</_:TestDefinition>
|
是否还记得,我们在上一篇文章中介绍了一种工具,该工具可以从数据对象定义创建此类文件的示例。数据文件必须位于模拟 Customer 项目中,以便可以从类加载器中加载它们。我们已经提供了一些示例文件(在 MT_TestArchiveRetrievalJUnitWeb 项目中提供)。
在 Web 透视图的 Project Explorer 视图中,展开 Dynamic Web Projects => MT_TestArchiveRetrievalJUnitWeb => Java Resources => JavaSource。
右键单击 com.ibm.issw.archive.testData.Customer 文件夹,并选择 Copy。
展开 Other Projects => MT_MockCustomer,然后右键单击 com.ibm.issw.archive.mock.customer 和 Paste(图 26)。
图 26. 安装测试数据
现在我们已经将测试数据放在与 MockCustomer 类位置相同的目录树中;测试数据由此类的类加载器加载。如果您选择一个不同的包名称,则需要相应地调整测试数据文件的位置。
现在您已经具有了 loadData 操作的实现,及其可供下载的测试数据。打开两个数据文件 Customer001.xml 和 Customer002.xml,并记下即将下载的 Customer ID。
ScaMockBase 类的其他属性
您会注意到,在 ScaMockBase 中创建了 java.util.Logger 的一个实例,这使我们能够将跟踪和错误语句添加到实现中。
protected Logger m_logger = Logger.getLogger(this.getClass().getName());
而且,m_lastRequest 和 m_lastFaultName 用于实现我们先前描述的最后请求方案。稍后我们将在第二个模拟对象实现中使用它们。
E. 实现 getCustomerDetails 操作
现在我们可以返回到 MockCustomerImpl.java 并添加 getCustomerDetails 的实现。
将以下代码用作该方法的主体:
m_logger.fine("getCustomer " + customerId);
for (int i = 0; i < m_dataList.size(); i++){
DataObject candidate = (DataObject) m_dataList.get(i);
int candidateId = candidate.getInt("customerId");
m_logger.finest("candidate " + candidateId);
if ( candidateId == customerId.intValue()){
m_logger.finest("returning candidate ");
return candidate;
}
}
throw new ServiceBusinessException("NotFound");
|
观察该实现使用基础类中 m_logger 成员变量发出的跟踪事件。
保存您的实现。
F. 测试模拟对象
现在您已经有了一个具备一定功能的模拟对象,它可以提供 getCustomerDetails 方法和一组用于该对象的测试。现在,您可以将 MockCustomer 模块和测试部署到服务器,然后验证 MockCustomer 操作是否正确。
通过展开 MT_TestArchiveRetrievalApp 模块并打开其组装图,将测试模块连接到模拟服务模块。您将看到测试使用的独立引用。
选择 customer 独立引用,并在 Properties 视图中选择 Binding 选项卡。
单击 Browse 以显示 SCA Export Selection 对话框。选择 MockCustomer 模块提供的导出。(图 27)
图 27. 将 MockCustomer 连接到测试
利用同样的方法,将 I_MockServiceController 连接到同一模块。现在我们已使测试对我们的模拟对象使用两个接口。
图 28 将 MockCustomer 控制器连接到测试
现在我们可以将模块部署到服务器。
在 Business Integration 透视图的 Servers 视频中,右键单击 WPS server,并选择 Add Remove Projects 以弹出 Add and Remove Projects 对话框。
将 MT_TestArchiveRetrievalApp 和 MT_MockCustomerApp 添加到服务器,然后单击 Finish。
图 29. 部署用于测试的 MockCustomer
等待应用程序完成部署和初始化,然后执行测试。在 Web 透视图中,展开 MT_TestArchiveRetrievalApp 并选择 CustomerTest.java。
从 Run 菜单选择 Run... 以显示运行对话框。
选择 JUnit 并单击 New。
图 30. 运行 MockCustomer 测试
这将创建一个新的运行配置 CustomerTest,如图 31 所示。
图 31. 运行配置
添加 Cactus URL 规范:
-Dcactus.contextURL=http://localhost:9080/MT_TestArchiveRetrievalJunitWeb
在 Arguments 选项卡中,调整主机和端口以便与您的测试环境相适应。
单击 Run 启动测试。
图 32. Cactus 参数和执行
您应看到 JUnit 测试成功,而且控制台中还显示一些其他信息,这些信息显示了测试执行的数据比较(图 33)。
图 33. 测试成功
图 34. 控制台中的测试进度
花些时间研究一下测试文件。您会看到:
01-InitialiseCustomers,重置 MockCustomer 持有的客户列表。
02-LoadCustomers,读取我们安装的测试数据。注意测试数据的位置是如何在有效负载中指定的:
<service>I_MockServiceControllerPartnerRef</service>
<method>loadData</method>
<payloadString>testData/Customer</payloadString> |
03-GetCustomer,检索具有指定 ID 的客户,并将其详细信息与预期的结果文件 ExpectedCustomer.xml 的内容相比较。
04-GetBadCustomer,尝试检索具有无效 ID 的客户,并预期抛出特定的错误。
此测试演示了我们的 MockCustomer 能够提供不同响应,这些响应能够驱动业务流程沿不同的方向执行。
模拟 CustomerCommunication 对象
我们将通过构造 MockCustomerCommunication 对象来阐述模拟对象的第二个方面。在我们的业务流程中,CustomerCommunication 组件应通知 Customer 有关其检索请求的结果。在测试业务流程中,我们需要确定 Communication 组件是否是使用正确的数据调用的。我们将通过创建合适的模拟对象来启用此类测试。
创建 MockCustomerCommunication 组件
创建 MockCustomerCommunication 的技术与刚才创建 MockCustomer 对象时使用的技术相同。我们将在这里列出大致步骤,但仅详细介绍新信息;请参阅上面的详细说明和屏幕快照(如果适用)。
创建 MT_MockCustomerCommunication 模块,将依赖项添加到 L_Archive、J_ScaUtilities 和 L_ScaTest 库中。
按照下列附加步骤创建 MockCustomerCommunication 组件和实现:
- 向该组件添加 I_CustomerCommunicationService 接口。
- 在 com.ibm.issw.archive.mock.customercommunication 包中生成框架实现。
- 添加 I_MockServiceController。
- 使用 SCA 绑定生成两个接口的单个导出。
- 修改该实现以扩展 ScaMockBase 类,从而实现 I_MockServiceController 接口。
现在保存您的工作。应该不会出现错误。
实现该服务
现在我们需要考虑业务流程将要使用的服务操作。
打开您的实现类,并检查 sendRetrievalResult 方法。观察其中不存在有用的实现。
public void sendRetrievalResult(DataObject retrievalNotifcation) {
//TODO Needs to be implemented.
} |
我们要求此方法能够与 ScaMockBase 类交互,以便测试方法 checkExpectedRequest 和 checkNoRequest 可以验证此方法是否已使用正确的数据调用。我们可以通过修改代码做到这一点:
public void sendRetrievalResult(DataObject retrievalNotifcation) {
m_logger.info("sendRetrievalResult" +
XmlSdoUtilities.getDataObjectAsXml(retrievalNotifcation));
saveLastRequest(retrievalNotifcation);
} |
跟踪消息利用了类 XmlSdoUtilities 中的一个实用方法,因此您需要添加此导入语句:
import com.ibm.swservices.sca.utilities.XmlSdoUtilities;
调用 ScaMockBase 中的方法 saveLastRequest 是唯一必要的实现。确保您已经保存了所有更改。模拟对象现在已完成。
测试 BPEL 流程
现在我们可以将 BPEL 流程连接到我们创建的两个模拟对象,然后运行测试。
在 Business Integration 透视图中,打开 MP_ArchiveRetrieval 组装图并选择 CustomerService 导入。
在 Properties 视图中选择 Binding 选项卡并单击 Browse,弹出 SCA Export Selection 对话框。
从模拟 Customer 模块中选择导出并单击 OK。
检查在 Binding 选项卡中输入的值,了解它们如何引用您的模拟对象模块。
图 35. 将流程连接到 MockCustomer
利用相同的方法,选择 CustomerCommunicationService 导入并将其连接到 MockCustomerCommunication 模块。结果应如图 36 所示。
图 36. 将流程连接到 MockCustomerCommunication
保存修改的透视图。
我们还需要使测试能够调用模拟对象控制器,因此请打开 MT_TestArchiveRetrieval 组装图,并使用上面描述的技术确保导入已正确连接,总结如下:
表 2. 将测试连接到模块
| 导入 | 模块名称 | 导出名称 |
|---|
| I_MockServiceControllerPartner | MT_MockCustomer | MockCustomerExport | | I_MockCustomerCommunicationControllerPartner | MT_MockCustomerCommunication | MockCustomerCommunicationExport | | archiveRetrieval | MP_ArchiveRetrieval | Initiation | | customer(我们仅在运行模拟 Customer 测试时使用此导入) | MT_MockCustomer | MockCustomerExport |
现在我们已经准备好初始化测试。确保您的测试服务器已启动,并向其添加了四个项目:
- MP_ArchiveRetrieval
- MT_TestArchiveRetrieval
- MT_MockCustomer
- MT_MockCustomerCommunication.
在 Web 透视图中,展开 MT_TestArchiveRetrieval 并选择 ArchiveTest.java。选择 Run => Run... 以创建 JUnit 启动配置,请不要忘记指定 VM 参数:
-Dcactus.contextURL=http://localhost:9080/MT_TestArchiveRetrievalJunitWeb
图 37. 启动存档测试
JUnit 测试应无错误地完成。检查测试规范,提醒自己如何结合使用测试和模拟对象来验证流程行为。
结束语
我们了解了简单的模拟对象如何实现 BPEL 流程的可重复单元测试,并描述了一个简单的框架,该框架极大地简化了模拟对象的构造。将可重复的单元测试原则应用于 BPEL 开发可提高工作效率和开发质量。
本系列的其他文章
下载 | 描述 | 名字 | 大小 | 下载方法 |
|---|
| Sample components | BpelUnitTesting-v1.1.zip | 698 KB | HTTP |
|---|
参考资料
关于作者  | 
|  | David Artus 是 IBM Software Services for WebSphere 团队的成员,在英国 IBM Hursley Lab 工作。他从 1999 年就开始提供 WebSphere 咨询和指导服务。在 1999 年加入 IBM 之前,David 曾从事过多个行业的工作,包括投资金融、旅游和 IT 咨询。他所感兴趣的领域包括分布式系统的设计、对象技术和面向服务的体系结构。 |
对本文的评价
|