一、 Jtest简介
Jtest是一个卓越的自动化Java编码标准分析与单元测试工具。Jtest自动测试任何Java类或部件,而不需要您写一个测试用例、驱动程序或桩函数。只要点击一个按钮,Jtest自动测试代码构造(白盒测试)、测试代码功能性(黑盒测试)、维护代码完整性(回归测试)和静态分析(编程标准执行和指标度量)。不需要复杂的设置,Jtest能够立即使用并指出问题。如果您使用“Design by Contract”(契约设计)技术在代码中加入描述信息,Jtest能够自动建立和执行测试用例验证一个类的功能是否符合其功能描述。
Jtest能够帮助您防止错误,其可定制的静态分析特性让您能够自动执行超过240个软件业权威认可的500多条编程标准,建立和执行任何数量的定制编程标准,并对它们进行剪裁以适应特定的项目和团队。
本文解释了单元测试和编程标准执行等开发技术如何帮助您防止错误并提高软件可靠性,以及Jtest如何自动化这些技术使得它们能够实际应用到快速开发过程中去。
二、 基本术语
单元测试 —— 单元测试测的是独立的一个工作单元。在Java应用程序中,“独立的一个工作单元”常常指的是一个方法(但并不总是如此)。作为对比,集成测试和验收测试则检查多个组件如何交互。一个工作单元是一项任务,它不依赖于其他任何任务的完成。
框架 —— 框架是一个应用程序的半成品。框架提供了可在应用程序之间共享的可复用的公共结构。开发者把框架融入他们自己的应用程序,并加以扩展,以满足他们特定的需要。框架和工具包的不同之处在于,框架提供了一致的结构,而不仅仅是一组工具类。
API契约 —— 对应用编程接口(API)的一种看法,把它看作是调用者和被调用者之间的正式协定。单元测试常常可以通过证实期待的结果来帮助定义API契约。API契约的说法来自伴随Eiffel编程语言而流行的Design by Contract(契约设计)实践
三、 单元测试
1)Junit简介
单元测试这个术语描述的是检查一个工作单元的行为的测试。
Junit只是实现单元测试的代码框架。在1997年,Erich Gamma和Kent Beck为Java语言创建了一个简单但有效的单元测试框架,称作JUnit。Erich Gamma是编写经典《设计模式》一书的“Gang of Four”之一。Kent Beck则因他的开创性的软件开发方法“极限编程”而同样广为人知。junit属于Xuint中的一员。当然,单元测试的框架不只有junit而已,并且以junit为核心或以junit原理而衍生出的其他框架或工具有很多,如Cactus。Junit是一套框架,继承TestCase类,就可以用Junit进行自动测试了。
2)Jtest与Junit之间的关系
JTest执行单元测试的核心框架则是Junit,他只是一个继承于Junit并自主扩展的工具。
为什么这样说呢?
我们使用JTest为一个[ABC]项目中的所有类生成单元测试类。我们可以发JTest为我们自动生成了一个[ABC.jtest]的项目,这个项目下包含了所有生成的测试类。我们可以观察这个[ABC.jtest]测试项目,发现[ABC]项目中的每个[ClassXXX]都相应的生成了一个[ClassXXXTest]的测试类。而每个[ClassXXXTest]的测试类都会继承于本包中的一个[PackageTestCase]的抽象类。观察该抽象类,又发现每个[PackageTestCase]的抽象类都继承于一个[jtest]包中的名为[ProjectTestCase]的抽象类。该[ProjectTestCase]的抽象类则继承于[junit.framework.TestCase]。
因此出现了一个继承的依赖联系链:
[ClassXXXTest] è [PackageTestCase] è [ProjectTestCase]è [junit.framework.TestCase]
Jtest在继承Junit的同时,还实现了自身的扩展。它
3)单元测试
单元测试能够极大地改善软件质量,它是在最容易和成本最低的阶段帮助您检测和纠正错误。首先,单元测试最接近错误,它能够有效低帮助您检测在应用级测试中很难找到的错误。单元测试所关注的常常是方法是否满足API契约。就如同人们同意在某种条件下交换特定的货物或者服务所写下的契约,API契约被看作是方法接口的正式协定。方法要求调用者提供特定的值或对象,并(作为交换)会返回特定的值或对象。如果契约不能满足,那么方法就抛出异常来表明契约没有被遵守。如果一个方法的行为同预期不符,那么我们说这个方法破坏了契约。
4)执行单元测试
手工执行单元测试是困难、乏味和非常耗时的。
执行单元测试的第一步是使得一个类可测。需要两个步骤:
Ø 设计能够运行这个类的测试驱动程序。
Ø 设计桩(stub)函数,它们为被测类所引用的任何外部资源返回值,这些外部资源当前或者不存在或者无法访问。
建立测试程序涉及到建立一个新类,它只用于测试原始类。测试驱动程序应该包括下列性质:
Ø 一个指定设置和清除的标准途径。
Ø 一个选择个别测试和所有测试的方法。
Ø 一种分析预期或意外输出的手段。
Ø 一种错误报告的标准形式。
如果您的类引用的任何外部资源(如外部文件、数据库和CORBA对象等)当前尚不存在或无法访问,您必须建立桩函数,它们能够返回相似于实际外部资源能够返回的值。在建立这些桩函数时,您需要选择桩函数的返回值以便测试类的功能性,并完全覆盖整个类。
为了保证对类的测试更彻底和精确,可能需要作若干次修改和重写。一旦建立了测试驱动程序,您必须小心地检查它们以防止它们包含任何错误。测试驱动程序中的错误会破坏测试,但是您却不能孤立地测试一个类,且也无法测试驱动程序。
在使得一个类可测后,您需要设计和执行必要的测试用例。理想的情况是您需要测试类的构造(即执行白盒测试),测试类的功能性(即执行黑盒测试),然后在每次代码修改后执行回归测试,保证任何变化不影响类的完整性。
您大致上已经能够看到,手工单元测试需要消耗大量的时间、精力和资源,这就是为什么单元测试不能得到广泛应用的原因。Jtest的自动化测试过程能够极大地加速单元测试,并测试得更彻底更精确。您只需要简单地告诉Jtest一个需要测试的类或项目(一组被测类),然后Jtest能够自动检查每个类,生成相应的测试驱动程序以及任何必要的桩函数, Jtest能使用构造、功能和回归测试技术自动测试每个类,而且它还能对所有的.java文件执行静态分析。
5)白盒测试
白盒测试是检查一个类在结构上是否健全。它不是根据类的描述测试其行为,而是保证一个类不会垮掉,并且通过非预期的输入时运行正确。
白盒测试包括生成和执行测试输入,这些输入数据的设计依据类的内部结构,试图找出是否存在类的使用中能够让类崩溃的错误(在Java中等价于抛弃一个未捕捉到的运行时异常),以及是否存在任何代码缺陷可能导致代码出错。成功的白盒测试取决于测试输入的能力,是否能够尽可能全面地覆盖类的方法并发现能够引发类异常的输入。而Jtest生成的有效覆盖率通常都会达到50-100%。
及早防止和检测结构性问题,对Java开发人员来说尤为重要。在大多数编程语言中(如C和C++),一个非法操作通常导致程序的突然终止。而Java提供一个非常简单的机制,捕捉运行时出现的异常,并让程序继续运行,这样可以简化对操作系统和其它服务的调用。另一方面,运行时异常源自非法操作,指出了程序中的一个错误。因此让程序继续运行通常会导致比C++中的突然终止更坏的情形。程序保持运行,就像问题不曾发生一样,但它将进入一种不稳定的状态,可能会产生不正确的结果或破坏正在存取的资源。
虽然白盒测试是保证类和应用质量的关键步骤,但手工执行白盒测试的困难常常使开发队伍取舍两难,要么干脆跳过这一步骤,要么大大简化。有效地执行白盒测试需要开发人员确切知道什么测试用例对于全面执行被测类是必要的。这一切对于手工测试来说都是相当困难的。当前的研究结果表明一个典型的软件企业仅测试了其开发的30%的源代码。一个原因是编写测试很少走到的执行路径或边界条件的测试用例很困难。要达到有效的白盒测试所要求的覆盖性指标需要相当数量的路径被执行过。而手工编写能够测试所有路径的输入数据几乎是不可能的。
Jtest的测试生成系统专利技术(patent #5,784,553 & #5,761,408)为开发人员提供了一种省时有效的白盒测试方法。Jtest通过自动生成和执行能够全面测试类代码的测试用例,使白盒测试完全自动化。Jtest使用一个符号化的虚拟机执行类,并搜寻未捕获的运行时异常。对于检测到的每个未捕获的运行时异常,Jtest报告一个错误,并提供导致错误的栈轨迹和调用序列。Jtest的先进技术保证它能够自动测试类的所有代码分支,从而彻底检查被测类的结构。
换句话说,Jtest自动生成高质量的测试用例集合,发现尽可能多的结构性错误,而且:
Ø 不需要用户写一点测试脚本语言或测试用例。
Ø 不需要用户写测试驱动程序。
Ø 不修改源代码。
Ø 不要求完整的应用。
Jtest报告下列未捕获的运行时异常:
Ø 行为错误的方法:这些方法对于某些特定输入不会产生异常。必须修改这些代码。
Ø 非预期参数:这一问题出现在当某方法遇到非预期的输入(不知任何处理)而产生一个异常。这些问题的修正可以通过检查输入并产生一个Illegal Argument Exception (IAE)(输入非法的异常)。改正这类问题可以使代码更清晰更易维护。
Ø 行为正确的方法:这时,方法的正确输出是产生一个异常。在这种情形下,建议开发人员修改代码,将这类异常的产生置于方法的throw子句中。这会得到更清晰的代码并易于维护。
Ø 仅为开发人员使用的方法:在这种情况下,这些方法“不被假设”成处理Jtest生成的输入,开发人员是这些方法的唯一使用者,并且不传递这些输入参数。最好的办法是修改这些代码,让它产生一个IAE。这将带来额外的好处,使代码更易阅读。
总之,通过执行自动白盒测试,并提示上述类型的问题,Jtest能够为开发人员节省大量的时间并防止了错误。由于能够自动执行白盒测试的各个步骤,Jtest对开发人员来说是非常实用的,为了保证质量可以经常执行这一综合性测试。更进一步,使用测试生成系统技术产生的测试输入,Jtest使得白盒测试比手工测试更精确更有效。
白盒(构造)测试验证对一个类的非预期输入不会导致程序的崩溃。为执行白盒测试,您需要设计和执行根据类的内部结构编写的测试输入,检查是否存在会导致类运行失败的任何可能的对类的使用,以及是否存在某些编程缺陷可能会导致代码更容易出错。白盒测试能否成功的关键取决于测试输入的能力,是否能够尽可能全面地覆盖类的方法,并找出引起未捕捉到的运行时异常的输入。
虽然白盒测试是保证类和应用质量的一个关键步骤,但手工执行的难度通常会使开发人员望而却步或草草了事。有效地执行白盒测试需要我们能够确定要完全检查被测类那些测试用例是必需的,这对于手工测试来说是太难了。目前的研究表明,典型的公司只测试了其开发的30%的代码,而其余的70%从来没有被测过。一个原因是编写能够测试很少执行的路径或极端的条件的测试用例很困难。例如,一个典型的1万行代码大约有1亿条可能的路径;手工编写能够执行所有路径的测试输入是不可行的或者说几乎是不可能的。
Jtest使用独特的技术完全自动化白盒测试过程。Jtest分析每个被测类的内部结构,自动设计和执行能够完全测试类的结构的测试用例,然后确定每个测试输入是否会产生一个未捕获的运行时异常。对于检测到的每个异常报告一个错误信息并提供一个导致错误的栈轨迹和调用序列。
四、 JTest指南
1)创建一个示例项目
Ø 启动JTest,选择File> New> Project…。
Ø 选择Jtest下的Jtest Example Project,弹出创建的设置对话框后,直接Finish。
Ø 创建完成后,项目会自动以JTest视图呈现。此时可以观察项目下有一些包和类,而每个包中的类都隐含着各种问题,这是为在下面对JTest的功能演示提供帮助,我们可以观察包名,大概了解这些包中的类所隐含的问题,如dbc(契约设计)等。
2)编码标准检测
通过检测编码标准,你可以使代码中隐含的错误与不稳定的结构等,这些有可能导致程序功能性、性能和安全性的问题被发现。JTest7.5.59中内置有500多条业界和行业中的编码规则,涵盖了JAVA编码的多个方面。并且也已经内置与自定义了一些标准组,这些将帮助你进行有效的编码标准检测。
Ø 以下JTest7.5.59是编码标准的分类组,一共32个分类:
1. JavaBeans [BEAN]
2. 编码约定/ 惯例Coding Conventions [CODSTA]
3. 契约设计/ Design by Contract [DBC]
4. Enterprise JavaBeans [EJB]
5. 异常/ Exceptions [EXCEPT]
6. 格式化/ Formatting [FORMAT]
7. 碎片回收集/ Garbage Collection [GC]
8. 全面静态分析/ Global Static Analysis [GLOBAL]
9. 初始化/ Initialization [INIT]
10. 国际化/ Internationalization [INTER]
11. JAVA2 微型平台/ Java 2 Micro Edition [J2ME]
12. JAVA文档注释/ Javadoc Comments [JAVADOC]
13. JAVA数据库连接/ Java Database Connectivity [JDBC]
14. JavaServer Pages [JSP]
15. JavaServer Page Metrics [JMETRICS]
16. Junit测试用例/ JUnit Test Case [JUNIT]
17. 类结构/ Class Metrics [METRICS]
18. 不同特性/ Miscellaneous [MISC]
19. 命名约定/惯例Naming Conventions [NAMING]
20. 面向对象编程/ Object Oriented Programming [OOP]
21. 最优化/ Optimization [OPT]
22. 可能存在的BUG/ Possible Bugs [PB]
23. 简便/ Portability [PORT]
24. 安全/ Security [SECURITY]
25. 序列化/ Serialization [SERIAL]
26. Servlets [SERVLET]
27. Struts 框架/ Struts Framework [STRUTS]
28. 线程与同步/ Threads & Synchronization [TRS]
29. 从未使用的代码/ Unused Code [UC]
30. 安全(必要的许可)/ Security (License Required) [SLR]
31. 安全策略规范Security/ Policy Rules (License Required) [SPR]
32. Web安全(必要的许可)/ Web Security (License Required) [WSLR]
Ø 详细的标准规则与自定义组介绍,可以另外查看文档《JAVA 编码标准规范》。
Ø 选择Jtest Example项目中examples.eval包下的Simple.java类,然后在工具栏上点击JTest的执行图标。
Ø 执行后,JTest默认将使用 来检测代码,Default Configuration是内置的定义组,对编码标准、生成单元测试、执行单元测试都做了相关的设置。当然,在这里我们暂且只关心编码标准检测的问题,生成和执行单元测试将在后面做详细的介绍。执行时,观察Jtest的执行概要面板,以下是编码标准检测的执行。
Ø 图标 中右上角出现一个红色的×,这表明检测中发现了错误,如果没有发现错误,将会出现绿色的√。
Ø 面板中还显示了Elapsed time执行时间,Files checked检测的文件数,Files skipped跳过的文件数,Failed runs执行错误数,Violations found违反规则数,
Ø 执行中可以点击Stop来终止检测,执行完毕后可以点击Report…来查看检测报告。
Ø 检测执行完毕后,在下方选择并显示JTest窗体,检测出的问题已经分类并显示在窗体内,通过点击其中的树型菜单查看详细信息。
Ø 展开Fix Coding Standard Violations分支,你可以发现JTest检测出的违反编码标准的信息,通过观察这些信息让你分析和定位问题所在。
Ø 分支 与 是编码标准中的分类组规则,而这些分支下则是检测出的代码中违反这些规则的集合。(JTest7.5.59对500多条编码标准的规则进行了分类,一共有32个分类,前面已经例举了这些分类名)。
3)对检测结果的分类布局
Ø 在Jtest窗体里显示的信息中,布局是可以进行更改的,可以隐藏相应的分组信息用来更好的显示问题的分类。可以通过点击 下拉出布局的设置。默认情况下,JTest7.5.59是对Uersnames和Projects进行隐藏的,使用的是Detail模式显示。所以你可以通过这个操作来显示或隐藏信息,以满足你对分类方式的需求。
4)快速修复编码标准问题
Ø Ensure "switch" statements do not contain typos [PB.TLS-1] 是违反规则的简短描述,如果你想查看规则的详细描述,可以右键该描述,选择
Ø ,JTest将准确的定位到该规则的详细文档处。
Ø 你也可以双击[Line 52] Text label 'case10' may be a typo for 'case 10',或则右键该错误,选择 ,JTest将准确的定位到存在该错误的代码中。
Ø 代码中map ()方法的case10并非不合法,但却是一种错误,而且保留它会引起在值为10时候的错误。它在代码中从未使用Avoid unused labels [UC.AUL-3],也带来可能的错误Ensure "switch" statements do not contain typos [PB.TLS-1],因为我们可能需要的是case 10。
Ø 我们对检测出的问题,可以进行手工更改,但我们也可以通过 来快速修复该问题。对于Qiuck Fix...操作,是可以通过树关系进行的,意思是我们右键操作上级分支时,则包括了对下级分支在内的修复。我们必须注意,对不同规则JTest的修复结果是不同的,该case10一共违反了两条规则,因此选择哪条规则然后进行Qiuck Fix...结果是不同的。修复[UC]中的Avoid unused labels [UC.AUL-3],JTest将会删除掉从未使用的case10。而修复[PB]中的Ensure "switch" statements do not contain typos [PB.TLS-1],JTest将会用case 10来替代case10。所以对于不同的问题,应该区别对待,建议对检测出的问题逐个进行修复,在合适时,再在上级树分支进行一次性全部修复。
5)忽略编码标准问题
Ø 当你对检测出的问题需要忽略,而不进行修复时,你需要把这个问题与其他要修复的问题隔离开来。你可以通过右键的 进行对问题的忽略,操作后弹出对话框,此时需要你输入对该问题进行忽略的原因。该操作与Qiuck Fix...一样,能通过对上级分支的操作,一次性忽略下级分支的问题。
Ø Suppress Task…操作后,再执行一次检测,你会发现,检测通过了。JTest执行检测时已经忽略了这些问题。而当你需要查看你已经忽略哪些问题时,可以通过Jtest> Show View> Suppression Messages操作打开Suppression Messages窗体查看已经被忽略的问题。请注意,只有选择了相应的项目、包或类,这些范围下的被忽略的问题才会显示。
Ø 假如你需要移除被忽略的问题,重新让Jtest检测出来,你可以通过点击选择相应的问题,然后点击Suppression Messages窗体右上角的 红色×。
6)重新指派问题负责人
Ø 对于已经检测出的问题,或许属于不同的处理人负责,此时可以右键问题的分支或具体的问题,进行重新指派。点击 ,弹出一对话框进行设置。勾选Delete…,设置后问题会从窗体中隐藏,而不勾选情况下问题则保留可见。设置后再重新再检测时,窗体自动刷新,问题的负责人就会变更为设置后的人员。