swing 调试

    技术2024-01-25  113

    Swing是可用的更强大的GUI工具包之一。 它是可扩展的,可配置的和跨平台的。 但是,Swing的灵活性既是其主要优点,也是其很大的缺点。 使用Swing,您可以通过许多不同的方式构造相同的UI。 例如,您可以使用插图,空白边框或填充符在GUI组件之间放置空间。 鉴于Swing拥有大量的选项,理解现有的GUI就像编写一个新的GUI一样令人生畏,将其可视化外观映射到基础代码并非易事。 (尝试在读取几行使用GridBagLayout的代码的同时可视化GUI。)

    无论您是维护未编写的Swing GUI还是将第三方GUI组件集成到应用程序中,理解代码的明智方法都是编写测试。 您可以在编写测试时探索未知代码的内部。 作为有价值的副作用,您最终会得到一个测试套件,该套件可以帮助您在维护代码时防止引入回归。 对于第三方GUI组件,该测试套件可帮助您确定该库的新版本是否引入了任何行为更改。

    一个好的开始是编写功能测试,以了解GUI响应用户输入的行为。 为GUI编写测试比为非可视代码编写测试更为复杂,因为:

    理想情况下,测试必须是自动化的,但GUI是供人类(而非计算机程序)使用的。 涉及隔离类测试的常规单元测试不适用于GUI组件。 用GUI术语来说,“单元”涉及多个GUI组件的协作,而GUI组件本身可以由一个以上的类组成。 GUI响应用户生成的事件。 要测试GUI,您需要一种模拟用户输入的方法,等待直到所生成的事件已广播给所有侦听器,然后检查结果,以使GUI出现在用户面前。 编写模拟用户与GUI交互的代码可能很乏味且容易出错。 GUI布局的更改不应影响强大的功能测试。

    另一个问题是您必须已经知道要测试的GUI的结构和行为,否则您将不知道自动测试应使用哪些组件以及需要验证哪些组件。 通常,编写GUI测试必须知道:

    GUI中要测试的组件 如何在测试中唯一标识此类组件 在特定用例中组件的预期状态(或属性)

    您可以使用可视化设计工具(例如NetBeans Matisse)来弄清GUI的结构。 但是,这种类型的工具仅显示GUI的设计时信息,该信息可能与您在运行时看到的信息不同。 例如,响应于用户输入,可以使一些组件可见或不可见。

    传统的调试器无法帮助您在执行特定用例时了解GUI的状态。 当调试器在放置在Swing代码中的断点处停止时,GUI绘制被中断,从而导致GUI看起来像空白方块。 理想情况下,您希望在使用调试器逐步查看GUI 时看到其行为。

    幸运的是,当需要了解现有的Swing代码时,两个开源工具Swing Explorer和FEST-Swing可以帮助您快速上手。 本文向您介绍了这些工具,向您展示了如何结合使用它们来检查应用程序的GUI结构,测试其功能并确定潜在的问题。

    探索的应用

    对于本文的大多数示例,我将使用一个免费的,功能强大HTML编辑器HTMLDocumentEditor作为要测试的应用程序(请参阅参考资料 )。 如果您想亲自研究这些示例,则可以下载该应用程序和示例测试代码。 图1显示了运行中HTMLDocumentEditor:

    图1. HTML编辑器

    在编写GUI测试之前,您需要了解GUI的组成。 HTML编辑器非常简单,由一个文本区域和几个用于打开,保存和编辑HTML文档的菜单组成。

    了解每个组件的具体类型也很重要。 这将帮助您了解GUI组件通过其API提供的哪些操作或属性,以供您在测试中使用。 对于HTML编辑器,您需要确定文本区域是JTextArea , JTextPane还是自定义GUI组件。 确定GUI组件类型的一种方法是检查源代码。 根据GUI的实现方式,这可能是一件容易的事或具有挑战性的任务。 HTMLDocumentEditor的源代码易读且易于理解,对其进行快速检查即可发现该文本区域为JTextPane 。 但是在您的职业生涯中的某个时候,您可能会遇到很难理解的书面GUI代码。 发生这种情况时,您可以花费大量时间来解密该代码,也可以找到一种工具来高效地为您执行此操作。

    介绍Swing Explorer

    Swing Explorer使您可以直观地检查Swing GUI的内部(请参阅参考资料 )。 其简单,直观的UI使得在GUI中发现所有组件,调查它们的绘制方式,在任何时间点检查其属性等变得很容易。

    Swing Explorer作为独立应用程序以及Eclipse和NetBeans的插件分发。 推荐的使用方式是通过IDE插件。 在本文中,我将使用Eclipse插件(请参阅参考资料 )。

    安装该插件之后,我使用Swing Explorer启动HTML编辑器主类,如图2所示:

    图2.在Swing Explorer中启动的编辑器应用程序

    Swing Explorer提供了一些视图,可以帮助您了解Swing GUI的内部结构:

    显示组件层次结构的树状视图 受检查的GUI 一个选项卡式窗格,显示选定组件的属性(名称,大小等),并包括其他有用的有趣工具( 有关详细信息,请参阅参考资料 )。

    使用Swing Explorer可以轻松确定GUI的结构。 出于本练习的目的,假定您无法通过阅读源代码来找出用作HTML编辑器中文本区域的组件的类型。 使用Swing Explorer,您只需在组件树视图上选择文本区域,或在显示的GUI中单击组件本身。 在下面的图3中,Swing Explorer确认文本区域是JTextPane :

    图3. Swing Explorer显示选定组件的属性

    学习和测试应用程序行为

    明确要测试的GUI的结构后,下一步就是了解应用程序的行为,以便您知道要验证的期望。 您可以通过不同的方式来执行此操作:通过采访当前的最终用户,阅读应用程序的文档(如果有),或者只是自己使用该应用程序。

    首先,我将选择两个用例进行测试:

    打开一个HTML文件 更改文档字体的颜色

    现在,我准备开始编写功能GUI测试。

    功能GUI测试可验证应用程序是否按预期工作。 它着重于应用程序的行为,而不是GUI的外观。 以下因素对于创建可靠的功能GUI测试至关重要:

    能够模拟用户输入(键盘和鼠标) 具有可靠的机制来查找GUI组件 能够容忍组件位置或布局的变化

    入门:直接使用Robot

    为了确保自动测试能够真正模拟用户输入,您需要在操作系统级别生成“本机”事件,就像用户正在使用键盘和鼠标一样。 从1.3版开始,JDK就通过Abstract Window Toolkit(AWT) Robot为输入仿真提供了支持。 但是Robot使用屏幕坐标而不是Swing组件引用,因此直接在测试中使用它会使测试变得脆弱,这意味着布局上的任何更改都会破坏它们。

    而且AWT Robot太底层了; 它只知道如何单击鼠标按钮和按键。 您需要编写代码来翻译高级操作,例如将此组合框中的第三个元素选择为Robot操作。 这可能需要大量工作,具体取决于测试中所需的操作数量以及所涉及组件的不同类型。 此外,AWT Robot没有提供可靠的组件查找机制(例如带有文本“ OK”的查找按钮 )。 同样,您需要自己编写。

    通常,直接与AWT Robot一起工作需要大量的精力和时间。 在编写功能性GUI测试时,您需要专注于要验证的行为,而不是使GUI测试成为可能的基础管道。

    介绍FEST-Swing

    FEST(易于软件测试的夹具)Swing模块是一个库,可轻松创建和维护强大的功能GUI测试。 其主要功能包括:

    构建在AWT Robot之上,可模拟真实的用户输入。 紧凑,直观,可读的流利界面,简化了功能GUI测试的创建和维护。 清单1显示了如何对高级操作进行编码,在firstName文本字段中输入文本“ luke”,然后单击“ ok”按钮 :
    清单1. FEST-Swing的流畅界面
    dialog.textBox("firstName").enterText("Luke"); dialog.button("ok").click(); 验证GUI组件状态的断言方法。 清单2显示了一个断言,该断言验证名称为“ answer”的标签的文本为“ 21”:
    清单2. FEST-Swing的断言
    dialog.label("answer").requireText("21"); 促进强大的测试:布局更改不会破坏测试。 支持JDK中存在的Swing组件。 支持JUnit 4和TestNG。 提供正确的Swing线程使用验证。 使故障排除失败的测试变得更加容易。

    用FEST-Swing编写功能GUI测试

    现在,我了解了编辑器应用程序GUI的结构,收集了要测试的用例,并找到了可靠的测试工具,我准备编写功能性GUI测试。

    用例:打开HTML文件

    在HTML编辑器中打开文件涉及:

    选择文件>打开子菜单 在显示的文件选择器中选择要打开的文件 验证编辑器是否已加载文件内容

    清单3显示了此用例的代码:

    清单3.测试打开HTML文件
    public class HTMLDocumentEditor_Test extends FestSwingJUnitTestCase { private FrameFixture editor; protected void onSetUp() { editor = new FrameFixture(robot(), createNewEditor()); editor.show(); } @RunsInEDT private static HTMLDocumentEditor createNewEditor() { return execute(new GuiQuery<HTMLDocumentEditor>() { protected HTMLDocumentEditor executeInEDT() { return new HTMLDocumentEditor(); } }); } @Test public void should_open_file() { editor.menuItemWithPath("File", "Open").click(); JFileChooserFixture fileChooser = findFileChooser().using(robot()); fileChooser.setCurrentDirectory(temporaryFolder()) .selectFile(new File("helloworld.html")) .approve(); assertThat(editor.textBox("document").text()).contains("Hello"); } }

    详细地,这是清单3中的测试正在做的事情:

    第一行扩展了FEST-Swing的FestSwingJUnitTestCase 。 它提供了FEST-Swing Robot自动创建,正确的Swing线程验证(稍后会有更多介绍)以及资源的自动清理(释放打开的窗口,释放鼠标和键盘等)。 editor = new FrameFixture(robot(), createNewEditor()); 创建一个新的FrameFixture ,能够模拟用户在Frame上的输入,在其中查找组件(使用各种搜索条件),并验证其状态。 editor.show(); 在屏幕上显示HTML编辑器。 @RunsInEDT说明了createNewEditor()方法被保证在事件分发线程(EDT)中执行。 return execute(new GuiQuery<HTMLDocumentEditor>()在EDT中创建HTMLDocumentEditor的新实例。 在editor.menuItemWithPath("File", "Open").click(); ,FEST-Swing模拟用户单击文件>打开子菜单。 在JFileChooserFixture fileChooser = findFileChooser().using(robot()); ,FEST-Swing找到HTML编辑器启动的“打开文件” JFileChooser 。 在接下来的三行中,FEST-Swing模拟用户选择位于系统临时文件夹中的helloworld.html文件。 assertThat(editor.textBox("document").text()).contains("Hello"); 通过检查文件是否包含单词Hello来验证文件是否已在编辑器中加载。

    请注意,清单3按名称( editor )查找JTextPane 。 这是在测试中查找组件的最可靠方法。 它可以确保即使将来GUI的布局发生更改,组件查找也不会失败。

    用例:更改文档字体的颜色

    要验证HTML编辑器将文档字体的颜色更改为黄色,您需要:

    选择颜色>黄色子菜单 在编辑器中输入内容 验证输入文本的颜色为黄色

    清单4显示了如何使用FEST-Swing做到这一点:

    清单4.测试更改文档的字体颜色
    @Test public void should_change_document_color() { editor.menuItemWithPath("Color", "Yellow").click(); JTextComponentFixture textBox = editor.textBox(); textBox.enterText("Hello"); assertThat(textBox.text()).contains("<font color=\"#ffff00\">Hello</font>"); }

    到目前为止,我已经展示了如何测试简单的GUI组件,例如菜单和文本字段。 接下来,我将介绍一个不太直接的测试方案。

    更复杂的测试

    为了演示FEST-Swing直观而紧凑的API,我将使用Swing的高度复杂的组件之一JTable 。

    我将使用TableDialogEditoDemo从Sun的Swing指南应用程序(参见相关主题 )。 该应用程序使用带有自定义编辑器的JTable : JComboBox es和JCheckBox es,如图4所示:

    图4. TableDialogEditDemo

    举例来说,我将编写一个模拟用户在第0行的组合框中选择第二个元素的测试。要执行的测试动作是:

    如有必要,请向上或向下滚动表格以使该行可见。 单击第0行第2列的单元格。 等待组合框出现。 找到组合框,然后单击它。 从组合框中选择第二个元素。

    那只是我需要编码的动作的高级描述。 编写实际代码并不是一件容易的事。 幸运的是,FEST-Swing的API使此任务非常简单,如清单5所示:

    清单5.在第0行的组合框中选择第三个元素
    dialog.table.enterValue(row(0).column(2), "Knitting");

    FEST-Swing可以简化GUI测试的编写和读取,甚至是复杂的GUI测试。

    秋千穿线

    Swing是一个单线程UI工具箱。 因为它不是线程安全的,所以所有Swing代码都必须在EDT中执行。 如官方文档所述,从多个线程调用Swing代码可能会导致线程干扰或内存一致性错误(请参阅参考资料 )。

    Swing的线程策略声明:

    必须在EDT中创建Swing组件。 除非您调用记录为线程安全的方法,否则必须在EDT中访问Swing组件。

    尽管听起来很简单,但是违反这些规则太容易了。 Swing不提供任何运行时检查来检查EDT的使用是否正确,并且在大多数情况下,您可以摆脱显然是“表现良好”的Swing UI的破坏者,这些UI违反了这些规则。

    Swing Explorer和FEST-Swing都提供了对发现违反Swing线程策略的支持。 图5显示了Swing Explorer的EDT监视器。 执行应用程序时,EDT监视器可以报告EDT访问冲突。

    图5. Swing Explorer的EDT监视器

    FEST-Swing提供FailOnThreadViolationRepaintManager来检查是否存在EDT访问冲突,如果检测到任何测试,则强制测试失败。 配置很容易:将其放置在标有@BeforeClass批注的设置方法中,如清单6所示:

    清单6.安装FailOnThreadViolationRepaintManager
    @BeforeClass public void setUpOnce() { FailOnThreadViolationRepaintManager.install(); }

    另外,UI测试可以将已经安装FailOnThreadViolationRepaintManager FEST-Swing的FestSwingTestngTestCase或FestSwingJunitTestCase子类FailOnThreadViolationRepaintManager 。 FEST-Swing还提供有用的抽象,以确保在EDT中完成对Swing组件的访问。 有关更多信息,请参阅参考资料 。

    对失败的GUI测试进行故障排除

    无论用于编写功能GUI测试的库如何,此类测试都容易受到与环境相关的事件的影响。 FEST-Swing也不例外。 例如,计划的防病毒扫描可能会弹出一个对话框,阻止您正在测试的GUI。 FEST-Swing Robot将无法访问GUI,最终将超时,从而导致测试失败。 失败的原因不是编程错误。 这只是不好的时机。

    FEST-Swing的一项有用功能是它能够在测试失败时对桌面进行屏幕截图。 您可以在IDE中执行单个测试时,可以将该屏幕截图自动嵌入到JUnit或TestNG报告中,或保存在目录中。 图6显示了GUI测试失败的JUnit HTML报告。 请注意,链接FEST-Swing已添加到桌面的屏幕快照中,该链接是在测试失败时获取的。

    图6.从失败的测试到桌面截图的JUnit HTML报告链接

    导致测试失败的另一个典型原因是组件查找失败。 推荐的查找组件的方法是使用它们的唯一名称。 有时,您需要测试的GUI的组件没有唯一的名称,从而迫使您使用自定义搜索条件。 组件查找失败有两种类型:

    找不到GUI组件。 例如,假设您正在寻找名称为firstName的JTextField ,但是原始开发人员忘记将该名称分配给组件。 在这种情况下,FEST-Swing在抛出的ComponentLookupException包括可用的组件层次结构,从而更容易发现失败的原因。 在此示例中,您可以检查组件层次结构以查看是否为JTextField指定了正确的名称,或者是否确实将JTextField添加到了GUI。 清单7显示了嵌入在ComponentLookupException中的组件层次结构:
    清单7.包含组件层次结构的ComponentLookupException
    org.fest.swing.exception.ComponentLookupException: Unable to find component using matcher org.fest.swing.core.NameMatcher[name='ok', requireShowing=false]. Component hierarchy: myapp.MyFrame[name='testFrame', title='Test', enabled=true, showing=true] javax.swing.JRootPane[] javax.swing.JPanel[name='null.glassPane'] javax.swing.JLayeredPane[] javax.swing.JPanel[name='null.contentPane'] javax.swing.JTextField[name='name', text='Click Me', enabled=true] 清单7中的组件层次结构可以帮助您得出结论,即原始开发人员给JTextField赋予了错误的名称。 相反的firstName ,目前的名字是name 。 找到了多个GUI组件。 如果多个GUI组件符合给定的搜索条件,则会发生这种情况。 例如,名称firstName可能被偶然赋予了两个JTextField 。 当查找名称为firstName的JTextField时,查找失败(并且最终也导致测试),因为两个组件的名称相同。 为了帮助您诊断问题,抛出的ComponentLookupException显示找到的所有匹配组件,如清单8所示:
    清单8. ComponentLookupException包含与某些搜索条件匹配的组件列表
    org.fest.swing.exception.ComponentLookupException: Found more than one component using matcher org.fest.swing.core.NameMatcher[ name='firstName', type=javax.swing.JTextField, requireShowing=false]. Found: javax.swing.JTextField[name='firstName', text='', enabled=true] javax.swing.JTextField[name='firstName', text='', enabled=true]

    有时,检查抛出的ComponentLookupException的组件层次结构可能会具有挑战性,尤其是在处理包含大量组件的GUI时。 再次,Swing Explorer可以提供很大的帮助。 如图所示,您可以通过直接单击组件来选择和检查组件层次结构中任何组件的属性。 在Swing Explorer的GUI中,大型组件层次结构比ComponentLookupException提供的基于文本的表示要容易得多。

    结论

    Swing的功能是以复杂性为代价的; 了解Swing代码可能与编写代码一样具有挑战性。 编写用于探索未知GUI代码的测试比编写针对非可视代码的测试更为复杂。 幸运的是,Swing Explorer和FEST-Swing可以帮助您减少繁琐的工作,并在此过程中进行猜测。 使用Swing Explorer,您可以在应用程序运行时探索GUI的结构。 一旦了解了要测试的GUI的结构和行为,就可以使用FEST-Swing的紧凑直观的API编写功能性GUI测试。 除了流畅的API外,FEST-Swing还提供对Swing线程和功能的正确使用的验证,这些功能可以帮助您在对失败的GUI测试进行故障排除时节省时间。 本文只是从头开始介绍了此功能强大的二重奏可让您完成的工作。

    与Swing Explorer和FEST-Swing一样有用,一个更好的解决方案是一种记录/回放工具,它以Java代码记录用户的交互,就好像它是由开发人员手动创建的一样。 记录/回放工具可在最短的时间内为您提供测试套件。 您与现有的GUI进行交互,并且所有用户生成的事件都记录在脚本中。 您稍后可以重播脚本,以针对特定场景重新创建用户交互。 现有记录/回放工具的主要缺点是对生成的测试进行昂贵的维护。 应用程序中的任何更改都需要重新记录所有测试方案。 同时,记录所有测试方案可以在测试相似方案时创建重复的测试代码。 录制的脚本通常很长,并且用缺少面向对象(OO)语言功能的专有语言编写。 重复动作的模块化是劳动密集型的并且容易出错,并且通常需要学习新的编程语言。

    通过使用基于流行和成熟的OO语言(即Java语言)的记录/回放工具,开发人员可以受益于使用功能丰富的IDE,这些IDE通过快速无聊,易于出错的任务(例如重构)来提高生产率并降低维护成本。和琐碎的。 这正是FEST项目团队目前正在从事的工作:一个回放/记录工具,该工具使用FEST-Swing Java API生成干净的面向对象的GUI测试。 我们希望在2010年第二季度之前可以预览此工具。


    翻译自: https://www.ibm.com/developerworks/java/library/j-swingtest/index.html

    相关资源:java swing可视化插件
    Processed: 0.028, SQL: 9