CSDN博客

img power163

第一次讨论

发表于2004/7/16 20:34:00  1313人阅读

JUnit的框架与应用专题讨论 

 

 

开源技术专题群> 异想者

杨树 整理 foxcrane 发布 

 

讲座提纲:

 

 

0.背景介绍

1Junit的使用方法 (实例)

2Junit的的主要类与方法 (理论)

3JmockJunit的扩展与使用方法 (实例、理论)

4strutsJunitStruts的测试示例 (实例)

5.现有测试工具的一览性说明和大概了解 (理论)

6PMD对静态代码的测试 (实例)

7.测试要注意的问题 (理论) 


 

0.   背景介绍

 

长期以来,我所接触的软件开发人员很少有人能在开发的过程中进行测试工作。大部分的项目都是在最终验收的时候编写测试文档。有些项目甚至没有测试文档。现在情况有了改变。我们一直提倡UMLRUP、软件工程、CMM,目的只有一个,提高软件编写的质量。所以我们这些凡人--在同一时间只能将注意力集中到若干点(据科学统计,一般的人只能同时考虑最多7个左右的问题,高手可以达到12个左右),而不能既纵览全局又了解细节--只能期望于其他的方式来保证我们所编写的软件质量。测试就是一种很好的保证软件质量的方式。

 

第一: 测试的目的很简单也极具吸引力,就是写出高质量的软件  

第二: 测试是一个持续的过程。也就是说测试贯穿与开发的整个过程中,单元测试尤其适合于迭代增量式(iterative and incremental)的开发过程 

第三: 在你不知道如何测试代码之前,就不应该编写程序。而一旦你完成了程序,测试代码也应该完成。除非测试成功,你不能认为你编写出了可以工作的程序。 

 

XP 中推崇的 Test First Design 就是基于以上的技术。如果你要写一段代码,则需要:

 

先用 junit 写测试,然后再写代码

写完代码,运行测试,测试失败

修改代码,运行测试,直到测试成功 

 

Java 下的 team 开发,一般采用 cvs(版本控制) + ant(项目管理) + junit(集成测试的模式:

 

  每天早上上班,每个开发人员从 cvs server 获取一个整个项目的工作拷贝。

  拿到自己的任务,先用 junit 写今天的任务的测试代码。

  然后写今天任务的代码,运行测试,直到测试通过,任务完成

  在下班前一两个小时,各个开发人员把任务提交到 cvs server

  然后由主管对整个项目运行自动测试,哪个测试出错,就找相关人员修改,直到所有测试通过。下班。。。 

 

由此我们可以看到junit面对的需求了。 

 


 

1Junit的使用方法  [参考:怎样使用Junit Framework进行单元测试的编写]

 

Junit是一个单元测试框架,框架是它的重点 。大家先看段代码:

 

package hibernateTest;

 

import junit.framework.Test;

import junit.framework.TestSuite;

 

public class TestAll {

 

// 定义一个suite,对于junit的作用可以视为类似于java应用程序的main

public static Test suite  {

 

//定义两个TestSuite

TestSuite suite = new TestSuite("root hibernate tests");

TestSuite childSuite = new TestSuite("child hibernate tests");

 

//组成一个测试目录树

suite.addTest(childSuite);

childSuite.addTestSuite( TestSample.class);

childSuite.addTestSuite( TestSample.class);

 

System.out.println("测试数据数量:"+suite.countTestCases  );

return suite;

}

 

这是我用来测试hibernate的一个测试用例管理的主程序。别急,还没到测试代码 J,我们先说说测试框架,它之所以成为一个测试框架而不是一个测试工具,就在于此。它的结果如下图所示:

 

我们可以看到图中的一个测试树结构。这段代码有以下几个点可以说一下:

 

定义一个suite,这个方法的声明 对于junit的作用可以视为类似于java应用程序的main

public static Test suite {……}

 

而这个目录树的目录就是下面的代码所示:

//定义两个TestSuite

TestSuite suite = new TestSuite("root hibernate tests");

TestSuite childSuite = new TestSuite("child hibernate tests");

 

而目录的组织和结点如下面的代码所示:

suite.addTest(childSuite); 

childSuite.addTestSuite( TestSample.class);

childSuite.addTestSuite( TestSample.class);

 

刚才说的是测试目录树的管理,现在准备说的是结点的书写, 也就是测试代码了:

 

package hibernateTest;

 

import junit.framework.TestCase;

import junit.framework.Test;

 

public class TestSample extends TestCase {

public void testMethod1 {

try {

boolean b = true;

assertTrue("错误则提示该信息",b);

} catch (Exception e) {

System.out.println("错误则提示该信息");

}

}

 

注意以下代码:

public class TestSample extends TestCase 

extends TestCase是关键部分。这样,它就成为了测试函数。

 

再看下一个关键部分代码:

public void testMethod1 {……}

testXXX也就是一个一个的测试方法。

 

这时请大家回顾一下,在一开始贴出的第二个图,也就是测试目录,查看一下,这些代码中的关键部分对树的目录结构的影响。


 

2Junit的的主要类与方法

 

首先在测试框架中,看看这个测试是如何完成的一个流程:

 

a)          TestCase.run 调用TestResult.run

b)         TestResult.run 调用TestResult .StartTest

c)          TestResult.run 创建一个Anonymous 类,实现接口Portectable

d)         Portectable. protect 方法中调用TestCase .runBare

e)          通过运行Portectable.runBare 调用runBare,通过Exception 捕获增加错误

 

解释如下:

首先,一个测试启动,但不会就直接调用测试代码,如果这样,那就不叫测试框架了J。而TestResult就是一个记录结果的类 ,而如果它直接调用测试代码,也会出问题。问题就是,在多个测试同时进行时,一个测试出错后,怎么办? [目前说到b]

 

接下来,就是Portectable出场了:  [目前说到c]

public abstract void protect() throws Throwable;

 

通过这种定义可以保证运行的时候如果出现任何Error Exception,都将被抛出而不会导致程序不能继续运行。 这也是c完成的工作。在这些准备工作做完之后,就可以调用测试代码。

 

在这些准备工作完成后,d中的TestCase .runBare就是如实的调用测试代码了。

最后通过Exception 捕获增加错误记录到TestResult中,就可以以文本或图形方式输出了。

 

在这个过程中还有一些隐含被调用的类和方法如下:

 

a)          方法throws 的是所有Error Exception 的祖先,通过这种定义可以保证运行的时候如果出现任何Error Exception,都将被抛出而不会导致程序不能继续运行。

b)         TestResult 用于运行并收集测试结果(通过Exception 捕获)。

c)          TestListener 接口的用途和它名称一样,用于监听。主要用于运行时刻监听,BaseRunner(所有运行类,如TestRunner)实现了这一接口。由于运行是通过TestResult 来实现,只要调用 TestResult.addListener 就可以增加监听。

d)         Assert断言就是是否达到你需要的结果 junit里有failexception两种失败 ,如果是被测试的程序中抛出异常,也认为测试失败。


 

3JmockJunit的扩展与使用方法  [参考:使用jMock辅助单元测试]

 

Jmock是对Junit的扩展,是以junit为基础的一个测试框架。Junit在完成单元测试时,有些功能实现起来,还是有麻烦的 ,如重复多次,还有junit的测试中不能加入参数,使得动态交互也成为一个问题。而Jmock的出现,使测试的使用更加的方便,而且大量减少了测试代码的书写。

 

例子如下:

mockStmt.expect(once).method("execute").with(eq(sql)).will(returnvalue(false)); 

 

解释如下:

expect    期待的执行次数,可以有 onceatLeastOnce  notCalled  三种

method   期待调用的方法名

with       方法需要的参数

will         返回值

 

其中的once, eq, returnvalue都是继承自MockObjectTestCase的方法。

 

执行后,Green,成功。尝试两次调用

assertFalse(stmt.execute(sql));

 

则提示错误。

 

相关的知识清参考mockObject 资料。 


 

4strutsJunitStruts的测试示例

 

StrtusTestjunit的扩展,使用它,不需要启动servlet容器就可以方便的测试struts应用程序(容器外测试)。它也是属于使用Mock对象测试,但是与EasyMock不同的是,EasyMock是提供了创建Mock对象的API,而StrutsTest则是专门负责测试Struts应用程序的Mock对象测试框架。除了容器外测试,StrutsTest也可以很方便的用容器内的测试。请看示例:

public class DeparmentDBActionTest extends MockStrutsTestCase {

 

public DeparmentDBActionTest(String arg0) {

super(arg0);

}

      

public void setUp() {

super.setUp();

//指明web应用的根

File contextFile = new File("D://Projects//fog//implement//web");

setContextDirectory(contextFile);

}

 

protected void teardown() throws Exception {

super.tearDown();

}

 

public void testDoAdd() {

//设置actionpath

setRequestPathInfo("[(!)]Edit");

   

//准备action所需要的formbean的参数

addRequestParameter("method", "doAdd");

addRequestParameter("pageId", "1");

addRequestParameter("dpname","测试部门");

addRequestParameter("dptype","测试部门");

addRequestParameter("yn","n");

 

//执行action

actionPerform();

 

//验证返回的forward

verifyForward("success");

}

}

 

有关细节,大家可以继续研究,今天主要是广而不深。


 

5.现有测试工具的一览性说明和大概了解

 

我们刚介绍的都是对于动态运行结果的测试,而对于静态代码也是存在测试工具的。下面这些只是做个介绍,有兴趣大家自己可以自己去看看:

FindBugs

PMD/CPD

Checkstyle

Jalopy/Jacobe

JDepend

JUnit

 

FindBugs:主要解决当前和潜伏故障,使用字节码分析来生成他的故障报告。 其中字节码分析是关键。

 

PMD:使用源代码分析,与编译器工作方式类似,允许你使用XPath引擎访问源代码解析器的输出。源代码分析是关键。

 

Checkstyle:与PMD相当相似,如名字所示,它主要是寻找格式故障方面。格式故障方面 是关键 

 

Jalopy / Jacobe:帮助解决事故等待发生故障和组织故障的代码格式程序。Jalopy是开源,但是他没有出现在活跃的开发中。Jacobe 可免费使用,但不是开源。

 

JDepend:在你的源代码周围生成了无数个度量,包括来自于主序列的传入和传出的耦合和分配。


 

6PMD对静态代码的测试 

 

对于静态代码测试,我推荐的当然是PMD,因为我就用了这个J。请看下面的例子:

public class testSample extends TestCase {

public void testMethod1() {

try {

int i=0;

boolean b = true;

assertTrue("成功",b);

} catch (Exception e) {

System.out.println("Yes, I catch u"); //应该到达点

}

}

 

 

请判断 ,那些代码有错?都是格式方面很小的问题。

 

1) int i=0 无用,可以删除

2) testSample,类名应该大写

 

你现在觉得PMD怎么样,是不是就像一个程序高手J
7
.测试要注意的问题

 

群内讨论[微雨心情]

测试这东西很奇怪,大家都做,但是好的和坏的差别太大了。测试驱动对人的要求很高,主要是:测试的边界、测试可重复(也就是不能这次测试了下次就重写参数)。常常,在边界问题上不能很好的把握(经验),使得测试的覆盖率很低(有效性覆盖,而不是方法)。 另外就是测试参数的相互干扰和重用问题,涉及到数据的清理和为了方便测试而建立的辅助类。 而且,一般的经验不丰富的人,不容易看到问题或者很自信,所以很自然的觉得没有问题而抵触。 另外就是测试驱动的同时,还可以采用测试和原始类互为测试的方式进行开发。还有就是测试驱动时顶上和底层两边走的使用。 因为,我虽然喜欢而且坚持测试,但我也存在时间问题,以及这些问题对我来说太自信的问题。测试就是“顶”,是使用者角度对类/方法的检查,那么实现就是底。通常,这个“底”可能是多层的,而在某些情况下,我可能不完全的测试优先。这个时候,两边走和互为测试就很好。比如,我原来开发我的Brave,我一个人,为了时间进度,我就这么来。当然,我对其很自信的才会忽略测试或者暂缓。两边一起这种方式是更实际的做法。J

 

下面是一些具体的编写测试代码的技巧或较好的实践方法: [Java开发者]
1.
不要用TestCase的构造函数初始化Fixture,而要用setUp()tearDown()方法。
2.
不要依赖或假定测试运行的顺序,因为JUnit利用Vector保存测试方法。所以不同的平台会按不同的顺序从Vector中取出测试方法。
3.
避免编写有副作用的TestCase。例如:如果随后的测试依赖于某些特定的交易数据,就不要提交交易数据。简单的会滚就可以了。
4.
当继承一个测试类时,记得调用父类的setUp()tearDown()方法。
5.
将测试代码和工作代码放在一起,一边同步编译和更新。(使用Ant中有支持junittask.
6.
测试类和测试方法应该有一致的命名方案。如在工作类名前加上test从而形成测试类名。
7.
确保测试与时间无关,不要依赖使用过期的数据进行测试。导致在随后的维护过程中很难重现测试。
8.
如果你编写的软件面向国际市场,编写测试时要考虑国际化的因素。不要仅用母语的Locale进行测试。
9.
尽可能地利用JUnit提供地assert/fail方法以及异常处理的方法,可以使代码更为简洁。
10.
测试要尽可能地小,执行速度快。

0 0

相关博文

我的热门文章

img
取 消
img