JUnit单元测试--小试牛刀

单元测试更多的是在开发阶段完成,开发人员每写一个函数的时候都会写相应的单元测试。对于java代码,普遍使用的是jUnit,根据jUnit可以自己相应的开发一套自动化测试框架。这个的前提是要学会junit,先知道怎么用,才能知道怎么为我所用。

学习JUnit的操作很简单,JUnit是一个敏捷编程的开发框架,他的设计很值得学习。这也是我学习JUnit的原因。

JUnit最大的特点就是:各个方法之间是独立的,一个方法的失败不会影响另一个方法的执行。

JUnit 3:是基于反射机制的方法,有命名的约束,必须以test开头。每一个类都继承了TestCase类,而TestCase的父类是Assert类。即所有测试类都是TestCase类的子类。

JUnit 4:引入了注解(annotation),通过解析注解就可以为测试提供相应的信息。@Test表明这是要执行的测试方法,不管测试方法的名字是什么,都会执行。每一个类可以不继承任何类,可以是一个普通类也可以去继承一个类或实现一个接口,要实现测试只需要在要测试的方法之前加@Test注释就可以了,但是仍然可以直接使用断言,静态导入import static org.junit.Assert.*;

为了更好地理解junit测试用例,我找了一个简单的例子来练习:

例如有这么一个类Greeting,根据每天不同的时间来返回不同的问候语:

package com.test.one;
 
public class Greeting {
    public static final String GREETING_MORNING = "Good Morning Sunshine!";
    public static final String GREETING_AFTERNOON = "Just a few more hours before quiting time!";
    public static final String GREETING_EVENING = "I am outta here";
    public static final int MAX_HOUR_MORNING = 12;
    public static final int MAX_HOUR_AFTERNOON = 17;
 
    private java.sql.Timestamp iGreetingTime = null;
 
    public Greeting(){
        this(System.currentTimeMillis());
    }
    public Greeting(long greetingTime) {
        super();
        iGreetingTime = new java.sql.Timestamp(greetingTime);
    }
    public String getGreeting(){
 
        if(iGreetingTime.getHours()<MAX_HOUR_MORNING){
            return GREETING_MORNING;
        }else if (iGreetingTime.getHours()<MAX_HOUR_AFTERNOON) {
            return GREETING_AFTERNOON;
        }else {
            return GREETING_EVENING;
        }
 
    }
 
}

  

我们需要测试这个类,看是否可以根据时间的不同,来呈现不同的问候语。创建junit的测试用例需要遵循一下几点:

1、Junit类需继承TestCase

2、setUp()和setDown()为每个方法准备或销毁测试装备

3、创建public的方法,方法名为test开头,如testMorningGreeting(),使用assert判断实际返回的值和期望值

4、super(testMethod)为每个测试方法生成实例。首先执行setUp(),然后执行testMethod ,最后执行tearDown()。

package com.test.test;
 
import java.sql.Time;
 
import com.test.one.Greeting;
 
import junit.framework.TestCase;
 
public class GreetingTest extends TestCase {
 
    protected void setUp() throws Exception {
        super.setUp();
    }
 
    protected void tearDown() throws Exception{
        super.tearDown();
    }
 
    public void testMorningGreeting() throws Exception {
        Time time = new Time(9, 0, 0);
        Greeting greeting  = new Greeting(time.getTime());
        assertEquals("the morning greeting expected", Greeting.GREETING_MORNING, greeting.getGreeting());
    }
    public void testAfternoonGreeting() throws Exception {
        Time time = new Time(12, 0, 0);
        Greeting greeting  = new Greeting(time.getTime());
        assertEquals("the Afternoon greeting expected", Greeting.GREETING_AFTERNOON, greeting.getGreeting());
    }
    public void testEveningGreeting() throws Exception {
        Time time = new Time(18, 0, 0);
        Greeting greeting  = new Greeting(time.getTime());
        assertEquals("the Evening greeting expected", Greeting.GREETING_EVENING, greeting.getGreeting());
    }
 
    public GreetingTest(String testMethod){
        super(testMethod);
    }
}

 

这个只是单个测试用例,junit也提供了测试套件组织想要执行的测试用例。

如果你已经有了两个测试用例,AddJobCmdImpTest、RemoveJobCmdImpTest等TestCase的子类别,如果想一次运行这两个测试,需要使用AllTest的类,代表一个测试套件。

public class AllTest {
    public static Test suite(){
        TestSuite suite = new TestSuite("Test for XXXX");
        suite.addTest(new TestSuite(AddJobCmdImpTest.class));
        suite.addTest(new TestSuite(RemoveJobCmdImpTest.class));
        return suite;
    }
}

以下是JUnit4的代码:

public class GreetingTest extends TestCase {
  
    @Before
    public void setUp() throws Exception {
        super.setUp();
         
    }
    @After
    public void tearDown() throws Exception{
        super.tearDown();
    }
     @Test
    public void ttrestMorningGreeting() throws Exception {
        Time time = new Time(9, 0, 0);
        Greeting greeting  = new Greeting(time.getTime());
        assertEquals("the morning greeting expected", Greeting.GREETING_MORNING, greeting.getGreeting());
    }
     @Test
    public void testAfternoonGreeting() throws Exception {
        Time time = new Time(12, 0, 0);
        Greeting greeting  = new Greeting(time.getTime());
        assertEquals("the Afternoon greeting expected", Greeting.GREETING_AFTERNOON, greeting.getGreeting());
    }
     @Test
    public void testEveningGreeting() throws Exception {
        Time time = new Time(18, 0, 0);
        Greeting greeting  = new Greeting(time.getTime());
        assertEquals("the Evening greeting expected", Greeting.GREETING_EVENING, greeting.getGreeting());
    }
}
 

 

关于JUnit4的一些知识点:

1)、注释

 注释  说明
@Before

用于方法注释,表示该方法在每个测试方法执行前执行一次,可用于一些初始工作

@BeforeClass 用于方法注释,该方法在所有测试方法运行前运行,且只运行一次,添加该注释的方法必须修饰为 public static void 且没有参数。
@Test

方法注释,表示测试方法。该方法有两个属性 

a: expected :该属性表示测试方法必须抛出一个异常,且异常的类型必须是该属性要求的类型,否则表示测试方法失败。也叫做异常测试。 例如:@Test(expected=IndexOutOfBoundsException.class) 

b:timeout 用于超时测试,表示该测试方法的执行时间如果超过了要求的时间则失败 单位为毫秒 

例如:@Test(timeout=100)

@Ignore 方法注释,表示会被忽略的测试方法
@After

方法注释,被注释的方法会在每个测试方法执行完成之后执行一次,如果其它的方法抛出了异常,该方法同样会被执行。主要用于释放在@Before方法中初始化的资源。

@AfterClass

方法注释,功能同@After,只不过是该方法释放的是@BeforeClass方法 

初始化的资源。且在所有的测试方法执行完成之后,只执行一次。

一个JUnit 4 的单元测试用例执行顺序为: 

@BeforeClass –> @Before –> @Test –> @After –> @AfterClass 

2)、failure 和error

      failure:是由于预期的结果和实际运行结果不同而导致的,例如当使用assertEqual()或其它assertXXX()方法断言失败时,就会报出failure,如果发现failure,你就要去检查你的测试方法或者是被测试方法中的填写的逻辑是否有误。简单点,就是预想到的。

      error:是填写程序是没有考虑到的问题,在执行测试的断言之前,程序就因为某种类型的意外而停止,比如说我们在操作数组的时候,因为存取超出索引就会引发ArrayIndexOutOfBoundsException,这时程序就会报error,程序将无法运行下去,提前结束,这个时候就要检查被测试方法中是否由欠缺考虑的地方。简单点,就是预想不到的,没有执行到断言。

JUnit4是JUnit框架有史以来的最大改进,其主要目标便是利用Java5的Annotation特性简化测试用例的编写。Annotation翻译成元数据。元数据是什么?元数据就是描述数据的数据。也就是说,这个东西在Java里面可以用来和public、static等关键字一样来修饰类名、方法名、变量名。修饰的作用描述这个数据是做什么用的,差不多和public描述这个数据是公有的一样。

@Test(expected=*.class)
在JUnit4.0之前,对错误的测试,我们只能通过fail来产生一个错误,并在try块里面assertTrue(true)来测试。现在,通过@Test元数据中的expected属性。expected属性的值是一个异常的类型


@Test(timeout=xxx):
该元数据传入了一个时间(毫秒)给测试方法,
如果测试方法在制定的时间之内没有运行完,则测试也失败。

@ignore:
该元数据标记的测试方法在测试中会被忽略。当测试的方法还没有实现,或者测试的方法已经过时,或者在某种条件下才能测试该方法(比如需要一个数据库联接,而在本地测试的时候,数据库并没有连接),那么使用该标签来标示这个方法。同时,你可以为该标签传递一个String的参数,来表明为什么会忽略这个测试方法。比如:@lgnore(“该方法还没有实现”),在执行的时候,仅会报告该方法没有实现,而不会运行测试方法。

@RunWith(Parameterized.class) 参数化测试。

你可能遇到过这样的函数,它的参数有许多特殊值,或者说他的参数分为很多个区域。比如,一个对考试分数进行评价的函数,返回值分别为“优秀,良好,一般,及格,不及格”,因此你在编写测试的时候,至少要写5个测试,把这5中情况都包含了,这确实是一件很麻烦的事情。只写一个测试函数,把这若干种情况作为参数传递进去,一次性的完成测试。

@RunWith(Parameterized.class)

public class SquareTest ...{

    private static Calculator calculator = new Calculator();

  private int param;

    private int result;    

@Parameters   

public static Collection data() ...{

        return Arrays.asList(new Object[][]...{

                ...{2, 4},

                ...{0, 0},

                ...{-3, 9},

        });

}

//构造函数,对变量进行初始化

public SquareTest(int param, int result) ...{

        this.param = param;
        this.result = result;

}

@Test   

public void square() ...{

        calculator.square(param);

        assertEquals(result, calculator.getResult());

    }

 }

下面我们对上述代码进行分析。首先,你要为这种测试专门生成一个新的类,而不能与其他测试共用同一个类,此例中我们定义了一个SquareTest类。然后,你要为这个类指定一个Runner,而不能使用默认的Runner了,因为特殊的功能要用特殊的Runner嘛。@RunWith(Parameterized.class)这条语句就是为这个类指定了一个ParameterizedRunner。第二步,定义一个待测试的类,并且定义两个变量,一个用于存放参数,一个用于存放期待的结果。接下来,定义测试数据的集合,也就是上述的data()方法,该方法可以任意命名,但是必须使用@Parameters标注进行修饰。这个方法的框架就不予解释了,大家只需要注意其中的数据,是一个二维数组,数据两两一组,每组中的这两个数据,一个是参数,一个是你预期的结果。比如我们的第一组{2, 4},2就是参数,4就是预期的结果。这两个数据的顺序无所谓,谁前谁后都可以。之后是构造函数,其功能就是对先前定义的两个参数进行初始化。在这里你可要注意一下参数的顺序了,要和上面的数据集合的顺序保持一致。如果前面的顺序是{参数,期待的结果},那么你构造函数的顺序也要是“构造函数(参数, 期待的结果)”,反之亦然。

打包测试

在一个项目中,只写一个测试类是不可能的,我们会写出很多很多个测试类。可是这些测试类必须一个一个的执行,也是比较麻烦的事情。鉴于此,JUnit提供了打包测试的功能,将所有需要运行的测试类集中起来,一次性的运行完毕,大大的方便了我们的测试工作。具体代码如下:

@RunWith(Suite.class)

@Suite.SuiteClasses(...{CalculatorTest.class, SquareTest.class})

public class AllCalculatorTests ...{}

大家可以看到,这个功能也需要使用一个特殊的Runner,因此我们需要向@RunWith标注传递一个参数Suite.class。同时,我们还需要另外一个标注@Suite.SuiteClasses,来表明这个类是一个打包测试类。我们把需要打包的类作为参数传递给该标注就可以了。有了这两个标注之后,就已经完整的表达了所有的含义,因此下面的类已经无关紧要,随便起一个类名,内容全部为空既可。

posted @ 2014-11-23 15:19  silenceer  阅读(2335)  评论(0编辑  收藏  举报