【IT168 技术文章】GUI 一般是基于模型-视图-控制器体系结构设计的。其中,视图是从模型中分离出来的。这种分离对自动化测试是一个挑战,因为我们很难检验模型中的状态改变是否在视图中得到了适当的反映,这样就产生了臭名昭著的 Liar View。诊断 Java 代码的这部分讨论的就是 Liar View 错误模式。

Liar, liar!

设想一下:您已经为一个分布式系统精心设计了一个极好的 GUI 程序,它包含了客户机请求的所有东西及其它一些东西。您已经让它运行通过了一个自动化测试套件的测试 ― 由于不变量的数量是个天文数字,因此,自动化测试是必须的。测试的结果是程序获得了一张“无错误的健康证明书”。

发布这个 GUI 的期限到了,但是,作为一个象您这样严格的程序员,只是为了发现错误的行为 ― 本该在自动化测试中就被捕捉到的行为,您启动了程序,对它做最后一次手工测试。但愿您能够避免这种情形。真的,您能够。

Liar View 错误模式

好的调试从好的测试开始。由于 GUI 程序中有大量的不变量需要检查,因此,自动化测试是必需的。但有时尽管已经通过了一套测试,在手工检查时,程序仍会出现本该由这些测试之一发现的错误行为。

快速跟踪代码

清单 1. JTable 及表模型 一个说明 Liar View 如何产生的简单 GUI 清单 2. 检查视图、模型及行的内容使断言说明真相

在分布式和多线程系统中,这种行为是常见的。在这些情况下,程序的“不确定性”本质经常就是原因所在。但在 GUI 中,却有另一种常见的原因 ― Liar View 错误模式。

症状

多数 GUI 程序测试,跟通常的程序测试一样,遵循下列步骤:

启动程序

检查程序状态的一些特征

尝试修改状态

检查状态是否已经按意愿被修改

但就象我已提到过的,有时对运行时程序行为的手工检查的结果会与测试得到的成功结果相矛盾:屏幕上可能显示一个队列,包含被测试(按设想进行)确认删除了的元素;而对象中可能包含报告显示已被更新了的陈旧数据。

类似这样的错误会使我们对自己的心智是否健全产生怀疑,或者更糟地陷入到康德的怀疑论哲学,怀疑起原因本身的有效性。

不要让这些发生在您身上。当正确对待原因时,它确实是有用的。尽管有反面的报告,但很少有程序员会在写代码时永久地丧失心智( 永久地是一个关键词)。

起因

找到这些错误的一个关键是要认识到,至少有一部分错误可以在测试套件中找到。

在测试 GUI 程序的过程中,错误最常发生的地方是在最后一步:检查状态是否已经按意愿被修改。原因是 GUI 一般是基于模型-视图-控制器(MVC)体系结构设计的。Swing 类库甚至把这种体系结构建到了 GUI 类自身的结构中。

在 MVC 体系结构中,程序的内部状态保存在模型中。视图响应改变模型状态的事件并相应更新屏幕图像。控制器把这两个组件连接在一起。

这种体系结构的优点是把视图从模型中分离出来,使得各自的实现可以独立修改。但是它对自动化测试方法却是一个挑战:我们很难检验模型中的状态改变是否在视图中得到了适当的反映。当这两者之间存在矛盾时,我们就会遇到一个 Liar View 错误模式的实例。

例如,考虑下面的简单 GUI。它在一列元素的内容被更新时显示其内容。 Controller 类的 main 方法被用作一个简单的测试。在实际的应用程序中,我把这个方法移到单独的测试类中,并将其挂到 JUnit 中(请参阅 参考资料)。

为了让我们能够使测试以慢动作方式进行,并在每个事件发生时对它进行手工检查,我添加了 pause() 方法和 PAUSE 字段。

清单 1. JTable 及表模型

1 importjava.awt.*;2 importjava.awt.event.*;3 importjava.util.Vector;4 importjavax.swing.*;5 publicclassController {6 privatestaticfinalintPAUSE=1;7 privatestaticvoidassert(booleanassertion) {8 if(!assertion) {9 thrownewRuntimeException("Assertion Failed");10 }11 }12 privatevoidpause() {13 try{14 synchronized(this) {15 wait(PAUSE);16 }17 }18 catch(InterruptedException e) {19 }20 }21 publicstaticvoidmain(String[] args) {22 Controller controller=newController();23 JFrame frame=newJFrame("Test");24 Model model=newModel();25 JList view=newJList(model);26 view.setPreferredSize(newDimension(200,100));27 frame.getContentPane().add(view);28 frame.pack();29 frame.setVisible(true);30 assert(model.getSize()==0);31 32 controller.pause();33 model.add("test0");34 controller.pause();35 model.add("test1");36 controller.pause();37 assert(model.getSize()==2);38 controller.pause();39 model.remove(0);40 controller.pause();41 assert(model.getSize()==1);42 controller.pause();43 System.exit(0);44 }45 }46 classModelextendsAbstractListModel {47 privateVector elements=newVector();48 publicsynchronizedObject getElementAt(intindex) {49 returnelements.get(index);50 }51 52 publicsynchronizedintgetSize() {53 returnelements.size();54 }55 publicsynchronizedvoidadd(Object o) {56 intindex=this.getSize();57 this.elements.add(o);58 this.fireIntervalAdded(this, index, index);59 }60 publicsynchronizedvoidremove(intindex) {61 this.elements.remove(index);62 }63 }64

您可能已经注意到这段代码有一个严重错误。如果我们运行这段测试代码,所有的断言都会是成功的,它表明已在列表中正确地添加或除去了项目。但如果我们通过某种方法,例如把 PAUSE 设成 1000,使运行速度降下来,那么我们就能以手工检查的方式运行测试。猜猜结果是什么?我们注意到,视图中不曾有项目被除去。

视图没被更新的原因是, Model 类中的 remove() 方法从未调用 fireIntervalRemoved() 来通知侦听器:模型的状态已经改变了。

但我们的测试方法中的所有断言都取得了成功。为什么呢?因为这些断言只是在模型中,而不是在视图中检查发生的更改。因为模型被适当地更新了,断言没能检测到遗漏的事件触发。

治疗及预防措施

防止这种错误模式的一种方法是: 只在修改模型的状态之后才检查视图的显式属性。虽然这种技术把我们能检查的属性限制在视图提供的范围内,但至少能使断言反映屏幕上实际正在发生的事情。

例如,我们可重写 Controller.main ,如下所示:

清单 2. 检查视图、模型及行的内容

1 importjava.awt.*;2 importjava.awt.event*;3 importjava.util.Vector;4 importjava.swing.*;5 publicclassController {6 . . .7 8 publicstaticvoidmain(String[] args) {9 Controller controller=newController();10 JFrame frame=newJFrame("Test");11 Model model=newModel();12 JList view=newJList(model);13 view.setPreferredSize(newDimension(200,100));14 frame.getContentPane().add(view);15 frame.pack();16 frame.setVisible(true);17 assert(model.getSize()==0);18 controller.pause();19 booleantoggle=model.toggle;20 model.add("test0");21 assert( toggle==!model.toggle);22 controller.pause();23 toggle=model.toggle;24 model.add("test1");25 assert( toggle==!model.toggle);26 controller.pause();27 assert(model.getSize()==2);28 view.setSelectedIndex(0);29 assert(view.getSelectedValue().equals("test0"));30 controller.pause();31 toggle=model.toggle;32 model.remove(0);33 assert(toggle==!model.toggle);34 controller.pause();35 assert(model.getSize()==1);36 view.setSelectedIndex(0);37 assert(view.getSelectedValue().equals("test1"));38 controller.pause();39 System.exit(0);40 }41 }42 classModelextendsAbstractListModel {43 booleanswitch=false;44 privateVector elements=newVector();45 ...46 publicvoidfireIntervalAdded(AbstractListModel m,intstart,intend) {47 super.fireIntervalAdded(m,start,end);48 this.switch=!this.switch;49 }50 publicvoidfireIntervalRemoved(AbstractListModel m,intstart,intend) {51 super.fireIntervalAdded(m,start,end);52 this.switch=!this.switch;53 }54 55 }56

通过使用 setSelectedIndex() 和 getSelectedIndex() ,我们对程序进行测试,测试虽只是稍有不同,但却有极大改善。修改后的测试不单检查模型,还检查视图,而且不只简单地检查行的数量,还检查所选行的内容。

直接核查视图的另一种方法是使用 Java Robot 类(在 Java 1.3 API 中有介绍 ― 请参阅 参考资料),使 GUI 的鼠标和键盘的物理操作真正实现自动化。

Robot 类还允许您对屏幕的局部进行快照,允许建立基于 GUI 视图实际物理布局的测试。当然,如果视图的物理布局不如其逻辑结构稳定,这种能力会成为一种缺点。每次物理布局改变时,都必须重写几个测试是很痛苦的。因此,对于视图不会频繁改变的成熟的 GUI,我推荐您使用 Robot 类作为测试工具。要测试逻辑方面的问题,您可以象我们上面所做的那样,调用视图的方法。

最后一条忠告:对视图对象中简单地把调用弹回到模型的方法要小心。这样做很快就会引入 Liar View。特别是 JTables,其中包含有很多这样的方法。

总结

以下是本周讨论的错误模式的小结:

模式:Liar View

症状:GUI 程序通过了测试套件的测试,但后来却显示出本该在那些测试中被消除了的行为。

起因:测试只在模型方面做检查,而没有在视图方面做检查。

治疗及预防措施:在视图方面做检查。

java 代码检验表不存在_诊断 Java 代码: Liar View 错误模式相关推荐

  1. java数据库表不存在_如果Java生产代码中不存在并在JUnit中确认,则创建数据库表...

    Code-Apprentice 2 java sql junit jdbc 我正在用Java编写数据库程序,并且想要创建一个表(如果它还不存在).我从中了解DatabaseMetaData.getTa ...

  2. java教务系统类设计_基于Java EE体系的高校教务管理系统的设计开发

    <基于Java EE体系的高校教务管理系统的设计开发>由会员分享,可在线阅读,更多相关<基于Java EE体系的高校教务管理系统的设计开发(3页珍藏版)>请在人人文库网上搜索. ...

  3. 【源码+图片素材】Java王者荣耀游戏开发_开发Java游戏项目【王者荣耀】1天搞定!!!腾讯游戏_Java课程设计_Java实战项目_Java初级项目

    王者荣耀是当下热门手游之一,小伙伴们是否想过如何制作一款属于自己的王者荣耀游戏呢? 本课程讲解了一个王者荣耀游戏的详细编写流程,即使你是刚入门Java的新手,只要你简单掌握了该游戏所需要的JavaSE ...

  4. java表底层生产工具_使用Java工具解决生产故障(一)-jcmd介绍

    1.简介 Java开发的应用程序在线上出现生产故障很常见,通常我们会在开发环境模拟此类故障,但偶尔也会遇到无法成功模拟的故障.那么我们就需要在生产环境上进行分析,定位故障产生原因.JDK1.7版本之后 ...

  5. java中各种vo举例_了解JAVA中的POJO,Entity,PO,VO,DTO,DM包括代码举例展示

    标签: 首先是从概念上来说 POJO,全称Plain Ordinary Java Object,我理解为极其单纯的Java对象 一般只有属性字段,无参构造以及get和set方法,也是指那些没有从任何类 ...

  6. java字符串拆分成数组_用Java实现JVM第八章《数组和字符串》

    小傅哥 | https://bugstack.cn 沉淀.分享.成长,专注于原创专题案例,以最易学习编程的方式分享知识,让自己和他人都能有所收获.目前已完成的专题有:Netty4.x实战专题案例.用J ...

  7. java 取栈顶元素_《Java实战之内存模型》详解篇

    内存是非常重要的系统资源,是硬盘和CPU的中间仓库及桥梁,承载着操作系统和应用程序的实时运行 JVM内存布局规定了Java在运行过程中内存申请.分配.管理的策略,保证了JVM的高效稳定运行 不同的JV ...

  8. java 多态判断非空_收藏Java 面试题全梳理

    脚本之家 你与百万开发者在一起 来源 | Java建设者(ID:javajianshe) 作者 |cxuan 如若转载请联系原公众号 Java 基础篇 Java 有哪些特点 并发性的:你可以在其中执行 ...

  9. 为啥JAVA虚拟机不开发系统_理解Java虚拟机体系结构

    1 概述 众所周知,Java支持平台无关性.安全性和网络移动性.而Java平台由Java虚拟机和Java核心类所构成,它为纯Java程序提供了统一的编程接口,而不管下层操作系统是什么.正是得益于Jav ...

最新文章

  1. golang error类型 简介
  2. ES业界优秀实践案例汇总
  3. 多线程 并发编程 看了这篇 终于了解了
  4. 【项目管理】风险分析
  5. ADempiere3.6.0LTS - 创建国家地区城市(基于Ubuntu Desktop 12.04 LTS)
  6. 北大师兄告诉你,怎样顺利完成自己的博士生涯
  7. Oracle在Linux上的预配置
  8. 暴力枚举——火柴棒等式(洛谷 P1149)
  9. script language=javascriptwindow.location.href=http://blog.securitycn.net/script
  10. Java HashMap 默认排序
  11. 2020-08-23 每日一句
  12. 计算机桌面壁纸被锁定,Win10桌面壁纸被锁定无法修改咋办? win10官网
  13. 余世雄 - 与上司沟通的7个技巧
  14. 與情分析系统,包括爬虫、文本摘要、主题分类、情感倾向性识别以及可视化...
  15. Unity --- 角色动画的使用以及按键控制角色运动
  16. 192.168.0.1登录入口
  17. pymysql获取要查询的字段名(列名)
  18. STM32F0 USB VCP数据发送丢包错位问题
  19. 计算机英语听力教程,致用英语听力教程2 《英语听力教程2》文本.doc
  20. 磊科nw705p虚拟服务器设置,磊科NW705P无线路由器上DHCP服务器设置操作步骤

热门文章

  1. python lambda表达式及用法_python lambda表达式简单用法
  2. 基于JAVA+SpringMVC+Mybatis+MYSQL的在线学习管理系统源码
  3. 基于JAVA+SpringBoot+Mybatis+MYSQL的婚纱影楼摄影网站
  4. hive 中文字符过滤_0650-6.2.0-通过UDF实现HiveImpala的中文拼音排序
  5. vmware克隆主机
  6. ArcGIS 10.2数字化线状要素时自己主动拼接成一条线
  7. My安卓知识5--百度地图api的使用,周边信息检索
  8. C# 给自己写的软件,加注册码功能。
  9. python类库31[使用minidom读写xml]
  10. 2-算法 矩阵 数组类