然后,编写测试用例以验证方面正确地与目标交互,如清单 4 所示:
清单 4. 与 mock 目标交互以测试建议
public void setUp() throws Exception {
super.setUp();
setUpMockHighlightUtil();
words = Collections.singleton("big");
mockTarget = new HighlightMockTarget();
mockTarget.setHighlightedWords(words);
}
//mock setup/tearDown omitted
public void testHighlighting() {
mockUtil.expects(once())
.method("highlight")
.with(eq("I am a big bear!"), eq(words))
.will(returnValue("highlighted text"));
String shouldBeHighlighted = mockTarget.getSomeString();
assertEquals(shouldBeHighlighted, "highlighted text");
}
注意在这个例子中,我结合了 mock 目标和 mock 对象(如在 第 III 节, 模式 2 中所描述的)。mock 目标为下面三种技术提供了基础。
模式 1. 通过扩展一个抽象方面并提供一个切点来测试建议
针对 :横切功能
概述 :Prework :如果有必要,重新编写方面,将它分为一个抽象方面以及 一个扩展它并具体化一个或者多个切点的具体方面。
有了抽象方面后,在测试类中创建一个 mock 目标。创建一个扩展了抽象方 面的测试方面。让测试方面提供明确针对 mock 目标的切点。这个测试通过查找 建议的已知副作用或者使用一个 mock 对象来验证方面中的建议是否成功。
示例:扩展 AbstractHighlighter
假定已经编写了 上一节中的测试代码。为了使测试通过,必须将 Highlighter 方面分解为一个抽象方面和一个子方面,如下所示:
public abstract aspect AbstractHighlighter {
public abstract pointcut highlightedTextProperties();
//... aspect continues
}
public aspect HighlightResults extends AbstractHighlighter {
public pointcut highlightedTextProperties() :
(
//...define pointcut as before
);
}
下一步,用一个只用于测试案例的方面扩展 AbstractHighlighter 方面。下 面我将它展示为测试案例的一个静态内部方面:
private static aspect HighlightsTestClass extends AbstractHighlighter {
public pointcut highlightedTextProperties() :
execution(public String HighlightMockTarget.*(..));
}
这个方面通过选择 mock 目标上所有的方法执行具体化了 highlightedTextProperties 切点。
优缺点
显然,这种测试过程是一种人造的情况。对一个假的对象测试假的方面。不 过,这只是表明测试的不是真正的切点。仍然可以验证建议和抽象方面所指定的 ITD 代码。在例子中,测试验证建议正确地编组了来自 ITD 的数据以及原来联 结点的返回值、将它传递给一个工具类并返回新的结果。这涉及了相当多的行为 。使用一个 mock 目标还使测试更清晰了,因为测试的读者不必阅读真正目标的 行为以及方面的行为。这种测试在为方面库编写单元测试时特别有用,因为只有 到了方面加入到具体的应用程序中以后才会有真实的目标。
如果将方面分解以利用这种模式的好处,那么您可能使它更具可扩展性。比 如,如果系统的新部分需要参与突出显示行为,那么它们可以扩展抽象的方面并 定义覆盖新情况的切点。这样,抽象方面与它所建议的系统解耦了。
模式 2. 测试与 mock 目标匹配的切点
针对 :横切规范和功能
概述 :这项技术与上一技术密切相关。这次不是扩展一个抽象类,而是编写 mock 目标,以使它匹配要测试的方面上的一个切点。可以通过检查方面是否建 议了 mock 目标来测试切点是否正确。如果要测试的切点过度专门化,那么可能 需要重新编写它,使得 mock 目标可以更容易地“预定”建议。
示例:基于一个标志接口测试切点
不是使突出显示方面成为抽象的,而是改写切点使它匹配 Highlightable 接 口上的方法执行:
public pointcut highlightedTextProperties() :
execution(public String Highlightable+.get*());
这种宽泛的切点匹配 Highlightable 上的所有 String getter。因为切点不 枚举特定的类,它已经匹配了 mock 目标上的 getSomeString() 方法。测试的 其余部分保持不变。
变化:使用一个注释
还可以编写切点以部分根据 Java 5.0 元数据进行匹配。例如,下面修改后 的切点匹配用 @Highlighted 注释修饰的方法执行:
public pointcut HighlightedTextProperties() :
execution(@Highlighted public String Highlightable+.*());
//you can apply the annotation in the source, or using the declare- annotation form
declare @method : public String SearchResult+.getTitle(..) : @Highlighted;
declare @method : public String SearchResult+.getProduct(..) : @Highlighted;
可以通过添加注释到其 getSomeString() 方法,使 mock 目标匹配新的切点 :
@Highlighted
public String getSomeString() {
return "I am a big bear!";
}
优缺点
这项技术还明确地分离了对方面行为与目标应用程序的行为的测试,使测试 变为更独立。如果切点还没有编写为容纳 mock 目标,那么应当通过重新编写它 们得到一个耦合更松散的方面。通过使方面足够一般化,可以影响测试类中的 mock 目标,还会保证它可以容易地让真实类参与方面的行为。
模式 3. 验证更复杂的切点(一个特殊情况)
针对 :横切规范和功能
概述 :上一个 mock 目标是简单的,但是也可以将 mock 目标编写为模拟复 杂的联结点(如 cflow())或者要影响的一系列联结点。
例子:模拟 cflow
假定希望对于下载的报告关闭突出显示。可以加入一个 highlightExceptions切点以排除由 ReportGenerator 调用的任何 getter,如 下所示:
public pointcut highlightedTextProperties() :
execution(public String Highlightable+.get*())
&& !highlightExceptions();
public pointcut highlightExceptions() :
cflow(execution(* ReportGenerator+.*(..)));
然后可以编写一个 mock ReportGenerator,它调用 HighlightMockTarget 以测试没有进行突出显示:
private class MockGenerator implements ReportGenerator {
public void write(OutputStream stream) throws IOException {
mockTarget.getSomeString();
}
}
public void testNoHighlight() throws Exception {
mockUtil.expects(never()).method("highlight");
MockGenerator accessor = new MockGenerator();
accessor.write(null);
}
不过,可以想像为更复杂的匹配情况(例如,somePointcut() && ! cflowbelow(somePointcut()))创建一个类似的 mock 目标。可视化工具不能给 出关于使用运行时检查的切点(如 cflow())的匹配的详细信息。用几个代表性 的 mock 目标检查这种切点是值得的。
结束语
当我看到未测试的代码时,觉得厌烦。没有好的测试集的代码通常有很多 问题,难于进行有信任度的改变,并且难以重构。不过,如果用方面实现横切行 为,那么有了测试(并理解)应用程序的横切关注点的新方法。
测试方面与测试对象很相似。这两种测试都需要将行为分解为可以单独测试 的组件。一个要掌握的关键概念是横切关注点分为两个区域。首先是横切规范, 它要回答的是关注点影响的是程序的哪些部分。其次是功能,它回答的是这些点 上会发生什么。如果只使用对象,那么这两个区域是交叉的,因为关注点在应用 程序中是纠缠在一起的。不过,使用了方面后,可以以一个领域为目标或者同时 分别以两个领域为目标。
将方面编写为可测试的,得到的设计好处与通过重构面向对象的代码来实现 可测试性所得到的好处相似。例如,如果将建议的正文转移到一个可独立测试的 类中,那么可以分析其行为而不用理解它横切应用程序的方式。如果修改切点 以使它们更能被 mock 目标访问,也使它们更可被系统中的非测试部分访问。 不管是哪种情况,都提高了系统整体的灵活性和可插入性。
不久之前,我听到了一个流传的说法,说面向方面的程序不能测试。尽管这 个谣传基本上已经消失,我仍然认为它是一个挑战。我希望本文表明不仅可以对 方面进行测试,而且在测试横切时,使用了方面后会好得多。