yanglilibaobao收录,使用标签:软件测试,时间:2007-8-6 15:07:51 | 相关网摘,我也收藏
如图 6,考虑到不同的需求,Abbot SWT提供了不同种类的匹配器,他们都实现了Matcher接口,如果有特别的需求,你也可以自行实现或者修改现有的匹配器的代码。
图6:Abbot SWT的匹配器
图 6 中展示了很多不同用途的匹配器,我们经常会使用 ClassMultiMatcher 和 TextMultimatcher。如果我们仔细的看看代码就明白 Abbot 是如何能够找到目标构件的,打开 abbot.finder.swt.BasicFinder 类,我们会看见 Abbot 会遍历 Hierarchy 上面的所有 UI 构件,直到找到匹配器的条件满足的构件返回。匹配器是如何匹配的呢?我们可以简单的看看 TextMatcher 的代码,在 TextMatcher 的 matches 方法中充满了诸如清单7中的判断代码。
清单 7:TextMatcher 的代码片断。
if (w instanceof Button) {
setWtext(((Button)w).getText());
}
if (w instanceof Combo) {
setWtext(((Combo)w).getText());
}
这说明所有TextMatcher对于不同的构件会有不同表现,比如 button就是 button的text,combo是选中项的text而tableItem的text则是所有列的text的一个数组。了解这些代码对你构造匹配器有着重要的意义,只有因势利导,你才能更好的使用Abbot。经常会有初学者将NameMatcher和TextMatcher 混淆,如果看看NameMatcher的代码就了解它是匹配构件的类型和变量名,而不是构件的文本。
细心的读者也许会发现一个问题,Abbot的匹配器过多的依赖于字符串的匹配,这要求最好所有的界面上的文本全部使用常量的方式定义(否则在不同的语言环境中,你的测试用例将寸步难行),这也符合Eclipse国际化的编程要求,可见使用Abbot测试用户界面,会迫使你编写国际化的插件(我们的新建java类测试用例为了简化,就没有做类似的处理)。
这里有个有趣的话题,我们可以通过使用键盘的快捷键避免使用太多的查找器,菜单的快捷键,Tab键和回车键的组合使用经常能够节省你不少的时间去寻找按钮和菜单,通过WidgetTester.actionKeyPress方法,你可以轻易的模拟键盘事件。同时这也是对你用户界面的Accessibility(易访问性)的极端测试,完全键盘操作是Eclipse界面设计的Accessibility的一项要求。如同我们刚刚提到的国际化的要求一样,这里我们的测试用例反过来能够对代码的质量提出一些强制要求,可见极限编程方法中优先编写测试用例的指导方针果然名不虚传(当然本文提到的国际化和易访问性只是一个无心插柳的副作用,优先编写单元测试代码会强制你的代码简单易读,接口和业务逻辑代码分离,层次清晰,结构明确,并迫使你重构代码,使设计更加细化。有兴趣的读者可以参考《重构,改善既有代码的设计》一书)。
Abbot 测试代码的同步执行
Java 语言本身就提供了多线程机制,因为很多的用户界面会通过不同的线程完成不同的任务,有些线程是同步的,有些线程是异步的,你很难保证你测试代码会被同步的执行。SWT要求对UI的操作都要放在新的线程中进行,在我们的JavaWizardTest测试用例中,我们必须使用新的线程去测试界面,同时为了保证测试的同步,我们要等待向导的出现,然后才能执行针对向导的测试代码。读者也许已经注意倒我们的测试用例中openJavaWizard这一方法并未在线程中被调用,那是因为在MenuItemTester中的actionSelectMenuItem操作中,选择菜单的操作已经启动了一个新的线程。
在我们的等待线程中,我们如果要执行UI构件相关操作的断言,就应当都把他们封装在一个Runnable里面使用“abbot.tester.swt.Robot.syncExec”保证他们被同步的执行,这最终使用到了“org.eclipse.swt.widgets.Dispaly”的syncExec方法。由于SWT单独有一个线程(主线程)处理界面显示,数据显示等,如果要在其他线程中操作界面元素,就必须使用Display的syncExec和asyncExec两个方法执行,即将另外线程的操作交给主线程处理,我们使用的syncExec方法表示要顺序的同步的执行相关的测试操作。对于形如“assertFalse(finishButton.isEnabled())”这样的断言,因为涉及倒了UI构件的操作,就需要包装在syncExec方法内,而形如” assertNotNull(finishButton)”这样的断言,则不需要任何包装,直接在测试线程的代码体内就可以执行。
研究 Abbot 的源码可以发现,Abbot 的测试器代码中都使用了 syncExec,所以我们唯一需要使用的地方就在执行UI构件相关的断言的地方,但是为了保证代码的统一和完整,我们尽量使用形如如本测试用例的写法。
在Abbot控制用户界面的测试同时,请不要使用鼠标或者键盘,同时尽量关闭可能会弹出对话框的后台程序,以免使 Abbot 失去用户界面的控制权。不过即使被别的用户界面打断,在有效的等待时间的内,你仍然可以手工帮助界面运行到等待线程的条件处,Abbot会检测到界面并能继续执行下去。如果你在测试 Eclipse RCP 应用,界面的产生可能会和网络条件或者数据量有关系,Abbot默认的等待时间可能不能满足需要。当在默认的时间到达前没有能够等到期望的界面时,Abbot 会抛出 WaitTimedOutError 异常。Abbot 默认的等待时间应该是一分钟,其值在 abbot.tester.swt.Robot 中有定义,见清单 8。
清单 8. 默认延时的定义
protected static int componentDelay =
Properties.getProperty( "abbot.robot.component_delay",defaultDelay, 0, 60000);
你也可以通过设置延时参数来调整你的测试代码,WidgetTester提供了一些等待方法,其中就可以通过参数来设置延时时间,见图 7。
图 7:WidgetTester的等待方法
如果你有特别的需要,不仅满足于等待对话框或者构件的出现(在RCP应用中常见的就是等待服务器端的返回数据),可以使用abbot.tester.swt.Robot的“wait(Condition condition)”方法。你只要实现自己特定需求的Condition接口就可以让用户界面在等待你期望的结果,如清单9所示,Condition接口相当的简单。
清单 9:Condition接口的定义
public interface Condition {
/** Return the condition state. */
boolean test();
/** Return a description of what the condition is testing. */
String toString();
}
你需要在 test 方法中检查条件,一旦条件满足就返回 true,否则返回 false, Abbot 的 wait 方法会不断去调用 test 方法查询条件是否满足,你在调用时设置超时条件(如 图 7 中的”wait(Condition,long)”方法),如果不设置,Abbot 仍然会使用默认的等待超时值。
抽丝剥茧:Abbot SWT 的体系结构和工作原理
通过以上的测试用例和概念介绍,你一定已经对 Abbot SWT 有了感性的认识,一个看似简单的测试用例,实际上并不简单,几乎包含了 Abbot 的所有主要内容。现在让我们继续深入下去,看看 Abbot SWT 的体系结构和工作的原理,以便更深刻的了解、掌握和使用它。
Abbot 测试器
我们首先从 Abbot 测试器开始,Abbot 测试器可以帮助我们对各种不同的UI构件进行测试,JavaWizardTest 测试用例使用到了文本测试器(TextTester)、按钮测试器(ButtonTester)、菜单项测试器(MenuItemTester)和通用的构件测试器(WidgetTester)。Abbot 为几乎所有的 SWT 构件提供了相应的测试器,我们可以从图 4 上看到这一点。
图 4:Abbot 提供的所有的 SWT 部件的测试器
从图 4 可以看出,Abbot SWT 的测试器相当的丰富,仅有少数的复合构件及 JFace 构件如 Section、Browser、Dialog 等没有测试类。但是这些复合构件都是由基本的UI构件组成的,我们可以通过简单的UI构件的测试完成符合构件的测试。当然你可能需要研究这些构件的源代码,才能找到组成他们的基本构件,从而使用相应的测试器对其进行测试。例如 对于Section,这个复杂的UI构件,目前Abbot并没有提供“SectionTester”,但Section的标题栏实际上是一个Label, 你可以使用查找器根据Section的标题找到Label,然后调用 LabelTeseter的测试方法actionClick来打开或者关闭一个Section,有兴趣的读者可以试试,这是笔者在实际项目中遇到的一个小问题,其他的问题可以类似的方法解决。
和几乎所有的JavaGUI 测试工具一样,所有的测试器最后都是通过java.awt.Robot的API去激活用户界面,我们可以通过查看Abbot SWT的源码,最终可以一直追溯到 java.awt.Robot 和RobotPeer,这正是JDK中里面为了方便界面测试提供给程序员的底层API,可见所有的Java图形用户界面测试工具无不是扩展自这个Robot。java.awt.Robot可以生成操作系统原生的事件和消息来创建自动测试,自运行的演示,或者出于其他目的需要让应用来控制鼠标和键盘,用途很是广泛,有兴趣的读者可以深入研究。
Abbot 查找器和匹配器
虽然通过测试器可以发出消息驱动UI测试的完成,但是如果没有查找器的帮忙,你很难获取到你要测试的构件的句柄。我们已经了解到Abbot主要是依靠查找器(Finder)和 匹配器(Matcher)去寻找测试目标,尽管他们在 Abbot的基础插件中已经有了实现,Abbot SWT插件还是要为SWT开发了专门的查找器和匹配器。我们可以通过图 5 了解Abbot SWT的查找器和匹配器的关系:
图5 :Abbot SWT的查找器和匹配器
如图,我们最常用的是BasicFinder和匹配器的配合,通过参数配置,BasicFinder可以支持广度优先和深度优先的查找。
循序渐进:编写高效的 Abbot 测试用例
至此我们已经能够基本掌握 Abbot 的用法,编写简单的测试用例,你也许没有注意到,我们的测试用例是否是有效的测试用例?我们是否可以用更简单的方式,编写更加高效的测试用例?在本文的这一部分,你将能够了解到这些内容。
使用自定义线程捕获异常
如果你使用JUnit测试过多线程程序,你会发现JUnit实际上不支持多线程的测试,所有在线程中的断言失败或者异常对该测试用例的结果没有影响。前面我们已经了解到,Abbot的测试通常是使用线程方式进行的,我们还沉浸在一开始就运行成功Java类向导测试用例的喜悦中,也许并没有发现,那并不是一个有效的测试用例。想知道事情的原委,读者可以将JavaWizardTest中findAndTestWizard方法的“assertFalse(finishButton.isEnabled()); ”更改为“assertFalse(!finishButton.isEnabled());”再次运行该用例,你将发现控制台中,JUnit捕获到了junit.framework.AssertionFailedError错误,但是用户界面却停滞不前,当你按下取消按钮帮助界面结束测试,你可以在JUnit视图中发现测试用例居然成功通过。
此时我们发现两个问题:
我们在线程中的断言和异常并不能真正的决定测试用例的成败,因此我们的测试用例是无效的。
在发生异常后,我们并没有考虑到如何使用户界面继续(我们需要手工帮助向导退出),后续的测试方法和测试用例将无法继续进行。
实际项目中,我们通常使用自定义的测试线程和增加在finally中对用户界面的清理 工作来解决上面两个问题,首先我们定义一个能够简单地显示运行是否成功的线程,请参见清单10。
清单10:测试线程TestThread
package abbottest.sample;
public class TestThread extends Thread {
private Throwable exp = null;
public boolean isSuccess() {
return (exp == null);
}
public Throwable getExp() {
return exp;
}
public void setExp(Throwable exp) {
this.exp = exp;
}
}
该测试线程能够记录在运行过程中发现的错误,并提供方法来检查在运行是否成功完成。如果我们将Abbot的测试代码封装在这样的线程中运行,就可以在主线程中判断测试线程运行是否有错误,从而实现测试的目的。
这样我们经过修改的代码,置于JavaWizardTest2.java,清单 11显示了使用测试线程的JavaWizardTest2的代码。
清单11:使用测试线程的测试用例
package abbottest.sample;
import junit.framework.TestCase;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Widget;
import abbot.finder.matchers.swt.ClassMultiMatcher;
import abbot.finder.matchers.swt.TextMatcher;
import abbot.finder.matchers.swt.TextMultiMatcher;
import abbot.finder.swt.BasicFinder;
import abbot.finder.swt.TestHierarchy;
import abbot.tester.swt.MenuItemTester;
import abbot.tester.swt.Robot;
import abbot.tester.swt.TextTester;
import abbot.tester.swt.WidgetTester;
public class JavaWizardTest2 extends TestCase {
public void testJavaWizard() {
final TestThread wizThread = new TestThread() {
public void run() {
WidgetTester.waitForShellShowing("New Java Class");
try {
findAndTestWizard();
} catch (Throwable e) {
e.printStackTrace();
setExp(e);
}
}
};
wizThread.start();
assertTrue(wizThread.isSuccess());
}
private void openJavaWizard() {
MenuItemTester menuItemTester =
(MenuItemTester) WidgetTester.getTester(MenuItem.class);
menuItemTester.actionSelectMenuItem( "&File/&New\tAlt+Shift+N/Class",
null, Display.getCurrent().getActiveShell(), 1000);
}
private void findAndTestWizard() throws Throwable{
TestHierarchy hierarchy = new TestHierarchy(Display.getCurrent());
final BasicFinder finder = new BasicFinder(hierarchy);
Button cancelButton=null;
try {
final Widget root = finder.find(new TextMatcher( "New Java Class"));
final Text nameText = (Text) finder.find(new ClassMultiMatcher(Text.class, 4));
final? Button finishButton=(Button)finder.find(root,
new TextMultiMatcher( "&Finish",1,Button.class));
cancelButton=(Button)finder.find(root,new TextMultiMatcher( "Cancel",1,Button.class));
abbot.tester.swt.Robot.syncExec(root.getDisplay(), null, new Runnable() {
public void run() {
TextTester textTester = new TextTester();
textTester.actionEnterText(nameText, "classname");
Robot.delay(1000);
assertFalse(!finishButton.isEnabled());
}
});
} catch (Exception e) {
e.printStackTrace();
throw(e);
}finally
{
WidgetTester.getWidgetTester().actionClick(cancelButton);
}
}
}
以上代码展示了新的测试线程的用法,并且我们在finnaly中退出该向导,可以保证该测试用例发生错误以后其余的测试用例能够继续运行。同时我们捕获的是Throwable,可以保证捕获到Abbot的异常和JUnit的断言失败。
至此一切无懈可击,此时你可以运行新的测试用例,等等,为什么运行以后虽然有异常,测试用例看起来还是成功的呢?请在 openJavaWizard();和assertTrue(wizThread.isSuccess());之间 加入清单12中的代码。
清单12:在测试方法中增加的代码。
while (wizThread.isAlive()) {
WidgetTester.getWidgetTester().actionDelay(100);
}
再次运行,终于成功通过测试,为什么会这样呢?这是因为测试线程的执行还没有完全完成,但是测试方法中的代码已经走到断言部分了,增加了以上的代码,可以保证在UI线程结束后在继续运行当前的测试方法内的代码,这就能保证测试方法内所有的代码(包括线程内的)都能够同步的被执行,有兴趣的读者可以研究一下Abbot的源码,了解详细的情况。如果你需要在一个测试方法中进行多个在新线程中顺序执行的界面测试行为,这种同步显得尤为重要,你都要使用以上的方法保证这些线程在当前线程的控制之下按顺序的执行,同时需要注意代码的写法,以保证所有的错误能够被当前线程捕捉到,真实地反映到当前的测试用例中。
基于以上方法,我们可以编写真正高效的测试用例,真正捕获到任何异常和错误,但是我们还有方法使你的测试用例更为简单。
使用 abbot.swt.eclipse 简化你的插件测试
abbot.swt在abbot的基础上增加了很多SWT的支持,类似的是,Abbot还提供了abbot.swt.eclipse插件为我们的Eclipse插件测试提供了一些便利的功能。abbot.swt.eclipse插件则在abbbot.swt插件的基础上增加了一些实用的测试方法和测试器(图1),极大的方便了对基于SWT的Eclipse插件用户界面的测试。
图1:新增的测试器
上图可以看出,abbot.swt.eclipse插件提供了Abbot SWT插件所没有的对话框测试器,能够方便的执行对话框的测试。同时在“utils”包中还提供了很多的实用类,见图2,
图2,abbot.swt.eclipse的utils包。
这些类的作用一目了然,的确可以帮助我们方便的进行插件的测试,例如,使用类InvokeNewWizard,我们可以很方便的打开新建Java类向导,只要一行语句就可以实现,见表清单13。
清单13:使用InvokeNewWizard打开新建Java类向导
InvokeNewWizard.invoke("Java/Class", Display.getCurrent().getActiveShell());
Abbot 还提供了更好的Abbot TestCase,内置一些测试器以及一些Shell之类Eclipse特有的对象,我们的测试用例可以从“abbot.swt.eclipse.tests.TestCase”派生,可以节省一些工作量。同时在“abbot.swt.eclipse.tests”包中还有很多的测试用例,读者可以学习一下,具有很大的参考价值。
总体上来说,使用abbot.swt.eclipse插件可以方便的进行Eclipse插件的用户界面测试,这里我们在使用方便之余也能体会到Eclipse插件编程思想中的插件分层思想。分层的思想,对于我们编程序和做项目具有很重要的意义,Eclipse的插件编程,严格的将界面、模型、核心、业务逻辑实用工具等分隔在不同的插件中实现,既能简化插件的开发,又能保证功能的简化。比如Abbot中的三个插件,abbot,abbot.swt,abbot.swt.eclipse,三者之间分工明确,协同工作就能完成Eclipse插件的用户界面测试,也可以单独使用。这里需要说明的是,abbot.swt.eclipse这个插件现在还没有太多的功能,不过我们可以预见,一定会为Eclipse插件的测试提供强大的支持。
更上层楼:构建复杂的插件测试用例
目前为止我们已经能够了解到Abbot SWT的全貌,也掌握了Abbot的一些高级用法,现在可以通过一个复杂的测试用例来看看如何在Eclipse环境中使用Abbot开发有效的用户界面测试用例。我们将使用Eclipse自带的一个插件示例作为被测试的插件,我们的测试用例就是要测试该插件的用户界面行为。
创建示例插件
我们将使用Eclipse的插件模板创建一个简单的示例插件,首先新建一个名为SampleView的插件项目,在向导的最后一页选择“Plug-in with a view”,点击“Finish”。该示例的目的是供使用者学习视图的编程和相关的操作,观察SampleView.java,我们发现很多的字符串变量,前面提到Abbot的搜索器和匹配器很大程度上依赖于字符串的匹配,为了便于测试,我们需要一定的重构,主要是字符串放到Message中,然后在测试用例中引用,重构后的代码请见附件。此时你可以启动Eclipse的Runtime,打开“Show View” 对话框,如图 3 所示。
图3:选择“Sample View”
选中“Sample Category”下的“Sample View”来打开该插件的视图,点击确认打开Sample View,该视图是一个简单示例,包含一个简单的Table,如图4。
图4:Sample View
双击table中的item会弹出对话框,在树节点上选择不同的右键菜也会弹出不同内容的对话框。这个就是这个示例插件的主要逻辑,对于初学者,这是一个很好的学习对象。对于我们,则可以用来作为一个待测试的插件,我们将使用Abbot构建一些端到端的测试用例,来测试该视图的功能。为了测试,你还需要添加给AbbotTest 插件添加新的Dependency,也就是SampleView插件。同时,请在SampleView插件的Runtime设置页中将如图的 2 个包作为导出的包,能够被测试插件引用到,如图5。
图5;导出sampleview的包
使用 Abbot 构建复杂的测试用例
通过对被测试对象的简单分析以后,我们需要创建两个测试方法,分别为测试视图中的双击事件和弹出菜单,同时需要一个方法能够打开视图。我们创建一个名为SampleViewTest的测试用例,详细代码请参考附件中的源码,下面分别介绍该测试用例中三个主要的方法。
显示视图
一旦我们运行 JUnit Plug-in Test ,JUnit 会启动一个新的 Eclipse Runtime,我们需要打开待测试的视图,因此清单 14 中的 openSmapleView 方法向我们展示了如何使用 Abbot API 打开一个视图。
清单14:SampleViewTest的openSmapleView方法
protected void openSmapleView() {
final Thread showViewThread= new Thread() {
public void run() {
WidgetTester.waitForFrameShowing( "Show View");
TestHierarchy hierarchy = new TestHierarchy(Display.getCurrent());
final BasicFinder finder = new BasicFinder(hierarchy);
try {
final Widget root = finder.find(new TextMatcher("Show View"));
final Text nameText = (Text) finder.find(new ClassMultiMatcher(Text.class, 1));
abbot.tester.swt.Robot.syncExec(root.getDisplay(), null, new Runnable() {
public void run() {
TextTester textTester = new TextTester();
textTester.actionEnterText(nameText,
Messages.SampleView_MSG_DLG_Title);
Robot.delay(1000);
wt.actionKeyPress(SWT.CR,Display.getCurrent());
Robot.delay(1000);
wt.actionKeyPress(SWT.CR,Display.getCurrent());
}
});
} catch (Exception e) {
e.printStackTrace();
fail(e.getMessage());
}
}
};
showViewThread.start();
wt.actionKey(SWT.ALT+
SWT.SHIFT+
(new Integer('q')).intValue(), Display.getCurrent());
Robot.delay(1000);
wt.actionKey( (new Integer('q')).intValue(), Display.getCurrent());
Robot.wait( new Condition(){
public boolean test() {
return isSampleViewOpened();
}});
}
注意我们用到了上文提到的快捷键的方式来激活显示视图对话框,并使用键盘操作来避免复杂的树操作,注意构件测试器的actionKey方法的用法,通过该方法,你可以方便的进行UI操作,而避免复杂的鼠标操作,对于测试鼠标操作无关的UI,使用快捷键操作可以避免书写冗余和重复的代码。
试着注释线程内的 delay 语句,你将发现,在树节点还未被选中之前按钮动作就被触发,所以界面无法继续下去。因此在使用Abbot时,我们需要树立一个观念,用户界面的打开或者显示绝对不是一瞬间的事情,我们经常会用等待方法保证用户界面在Abbot的指引下顺利的运行。
最后的Robot.wait方法向我们展示了自定义等待条件的用法,我们需要调用该方法确定视图已经被打开(因为视图的打开同样需要时间,如果不等视图完全显示就进行下一次操作,可能会导致错误)。在Abbot测试用例中你会经常使用该方法去等待业务操作的完成,实现一个简单的Condition接口就可以做到。
测试弹出菜单
使用打开视图方法可以打开示例视图,一旦确认示例视图被打开,我们就可以开始测试,清单15展示了弹出菜单的测试方法。
清单15:弹出菜单的测试方法。
public void testPopupMenu()
{
if(isSampleViewOpened()==false)
openSmapleView();
TestHierarchy hierarchy = new TestHierarchy(Display.getCurrent());
final BasicFinder finder = new BasicFinder(hierarchy);
try {
final TableItem item =(TableItem)finder.find(
new TextMultiMatcher(Messages.SampleView_One,1,TableItem.class));
final Menu m = item.getParent().getMenu();
Thread menuClick = new Thread() {
public void run() {
item.getDisplay().syncExec(new Runnable() {
public void run() {
new Robot().keyPress(KeyEvent.VK_ESCAPE);
}
});
}
};
menuClick.start();
// Right mouse click on TreeItem
itemTester.actionClick(item, 1, 1, "BUTTON3");
MenuItem[] aItems = new MenuTester().getItems(m);
Thread actionDialog = new Thread() {
public void run() {
WidgetTester.waitForFrameShowing("Sample View");
try {
final Widget root = finder.find(new TextMatcher("Sample View"));
Label label=(Label)finder.find(root,
new TextMultiMatcher(Messages.SampleView_Action2_executed,
1,Label.class));
assertNotNull(label);
Robot.delay(1000);
wt.actionKeyPress(SWT.CR, root.getDisplay());
} catch (Exception e) {
e.printStackTrace();
fail(e.getMessage());
}
}
};
actionDialog.start();
itemTester.actionSelectPopupMenuItem(aItems[1], 10, 10);
} catch (Exception e) {
e.printStackTrace();
fail(e.getMessage());
}
}
private boolean isSampleViewOpened() {
IWorkbenchPage activePage =
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
if (activePage.getActivePart() instanceof SampleView)
return true;
return false;
}
这里我们使用到了激活鼠标右键的操作:itemTester.actionClick(item, 1, 1, "BUTTON3"),该句语句中"BUTTON3"表示右键,这条语句能够执行右键单击的操作,从而显示右键菜单,类似的操作我们在Abbot的测试中会经常使用到。这里有个奇怪的menuClick线程,该线程的作用是点击键盘上的退出键,读者可以尝试注释该线程的代码,运行测试,观察用户界面的显示,你可以发现界面被阻塞在右键菜单处,无法选中菜单。在显示菜单后,执行menuClick线程就可以避免这种情况的出现。
读者也可以看见,我们会通过root参数缩小查找范围,这里的root就是弹出对话框的Shell。在“wt.actionKeyPress(SWT.CR, root.getDisplay());”语句中你同样需要使用该shell对象,该语句的意思是在弹出的对话框中按下回车键,显然比找到OK按钮,然后点击OK按钮要方便的多。同时因为测试线程只是进行了简单的判空操作,并没有直接执行UI构件的操作,所以我们不需要在使用Robot.syncExec方法执行测试代码,而是将其直接置于线程体内即可。在编写具体的测试用例的时候,我们应当尽量以简单易懂为原则,灵活的使用Abbot API,编写出简洁高效的测试用例。
测试双击事件
测试完右键菜单后,我们再看看如何测试表中的Item的鼠标双击事件,参见清单16的代码。
清单16:测试鼠标双击的方法
public void testDoubleClick()
{
if(isSampleViewOpened()==false)
openSmapleView();
TestHierarchy hierarchy = new TestHierarchy(Display.getCurrent());
final? BasicFinder finder = new BasicFinder(hierarchy);
Thread actionDialog = new Thread() {
public void run() {
try {
final TableItem item =(TableItem)finder.find(
new TextMultiMatcher(Messages.SampleView_One,1,TableItem.class));
assertNotNull(item);
wt.actionClick(item, 6, 3, "BUTTON1", 2);
WidgetTester.waitForShellShowing(Messages.SampleView_MSG_DLG_Title,2000);
final Widget root = finder.find(
new TextMatcher(Messages.SampleView_MSG_DLG_Title));
Label label=(Label)finder.find(new TextMultiMatcher(
NLS.bind(Messages.SampleView_Double_click,
Messages.SampleView_One),1,Label.class));
assertNotNull(label);
Robot.delay(1000);
wt.actionKeyPress(SWT.CR, root.getDisplay());
} catch (Exception e) {
????????? ?? e.printStackTrace();
fail(e.getMessage());
}
}
};
actionDialog.start();
//??safe join
while (actionDialog.isAlive()) {
wt.actionDelay(100);
}
}
双击事件的测试再次向我们展示了鼠标事件的用法,通过“wt.actionClick(item, 1, 1, "BUTTON1", 2)”这行代码,我们可以单击鼠标左键两次,注意如果不加以控制,这两次的点击会引起线程相关的问题,所以我们使用上文论述过的方法来控制代码能够同步执行。也就是我们首先启动测试线程,然后使用while方法等待,测试线程不执行完毕,当前线程就一直等待,这样可以保证的测试线程内的测试动作完全执行完成后再将控制权返回当前线程,继续运行。
本部分的代码展示一个复杂的测试用例中怎样进行一些复杂的测试,完整的测试代码请参见附件。读者也许已经意识到,编写复杂的Abbot测试用例会强迫测试人员对SWT用户界面的运行原理、事件、线程等机制有比较深入的了解,这也从另外一个方面促使你更加深刻的掌握SWT的用户界面编程。因此测试用例对我们的影响是多方面的,除了上文论及的国际化和易访问性,也会对我们的知识的深度进行一次洗礼,使你对被测试的代码和背后的运行原理,有更为深刻的认识。
总结
至此,我们已经能够了解 Abbot 的基本概念和原理,并能理解和执行一些测试用例。我们可以看出 Abbot 整个的逻辑非常的简单,无非是找到一些待测试的构件,执行相关的动作,比较测试的结果。如果组合使用这些简单的逻辑,就能够测试很复杂的界面。对于用户定制的特定的UI,比如使用 gc 直接绘制的构件,或者是一些特定的复合构件可能需要对 Abbot 做相应的扩展,你也可以分析 Abbot 的源代码,扩展相应的测试器来解决这些问题。在 Abbot 的测试中尤其需要注意线程同步的问题,文中我们很多地方都有讨论,读者在自己编写测试用例的时候需要注意。虽然编写端到端的用户界面测试用例比较麻烦,但是熟悉以后,你可以很快的写出高效的测试用例,显著提高你的开发和测试效率。
本文并没有讨论到Abbot的脚本和Swing的Abbot测试。各种各样的脚本在我们的编程中总是起着事半功倍的效果,通常我们使用的各种测试工具都会有脚本的概念,Abbot 也不例外。Abbot使用可以简单的xml文件记录测试过程,并提供了脚本编辑器来编辑和运行,有兴趣的读者可以从Abbot站点获取相关的资料。关于AWT和Swing的测试,Abbot的网站上提供了足够的示例,如果需要的读者可以访问Abbot站点。
从实用的角度来说Abbot插件的功能已经足够应付一般的Swing和SWT的测试,不过其针对Eclipse插件的测试相关的功能还在开发过程中,我们希望尽快能够有发布的版本,能够更全面的支持Eclipse插件的测试。
就像你为所有重要的功能编写了单元测试一样,我们建议你为重要的界面编写测试用例,尤其对于一些数据敏感的动态生成的用户界面,Abbot的测试非常重要。但是,我们不推荐像写单元测试一样写细粒度的用户界面测试,自动化的工具不能完全代替人去执行手工的测试。即使我们有100%覆盖的用户界面测试用例,也不能完全保证用户界面适用于真正的用户,何况,编写100%覆盖的用户界面测试用例是不可能的,UI的外观,易用性,单词的拼写错误,构件的对齐等很多问题,只能由人去测试。
但是Abbot等自动化测试工具的意义仍然非常重要,如同我们展示的一样,对于重复的界面测试,数据或者业务逻辑紧密联系的界面,界面的流程和操作等,使用工具或者脚本能够极大的减少测试人员的工作量。实际项目中,我们更倾向于通过UI的动作来测试业务逻辑,通过端到端的测试用例来实现功能的测试,除了自动化程度高以外,还可以避免代码的改变和业务逻辑的改变带来的测试用例的改变。对于一个设计良好的Eclipse插件,UI的变化频率应该远远的低于业务逻辑的变化(在某种的意义上),因此通过界面去测试业务逻辑是很好的一种选择。选择重要的流程,通过Abbot构建端到端的测试用例,能够覆盖到绝大多数的业务流程,这样的测试经过我们实践的证明是有效的。
http://www.51testing.com/?action_viewnews_itemid_17505_page_5.html