场景:从开始写代码至今,对于单元测试一直没有重视,但是也厌倦了了程序中的额System.out和log日志输出。单元测试使我看到了在开发过程中的安全性和便捷性,所以下决心好好整理下。

有感而发——《写有价值的单元测试》

1 基本概念

测试在软件生命周期中的重要性,不用我多说想必大家也都非常清楚。软件测试有很多分类,从测试的方法上可分为:黑盒测试、白盒测试、静态测试、动态测试等;从软件开发的过程分为:单元测试、集成测试、确认测试、验收、回归等。

在众多的分类中,与开发人员关系最紧密的莫过于单元测试了。像其他种类的测试基本上都是由专门的测试人员来完成,只有单元测试是完全由开发人员来完成的。那么今天我们就来说说什么是单元测试,为什么要进行单元测试,以及如更好的何进行单元测试。

1.1 什么是单元测试

单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。比如我们可以测试一个类,或者一个类中的一个方法。

1.2 为什么要进行单元测试

为什么要进行单元测试?说白了就是单元测试有什么好处,其实测试的好处无非就是减少bug、提高代码质量、使代码易于维护等。单元测试有什么好处请看一下百度百科中归纳的四条:

1、它是一种验证行为。
程序中的每一项功能都是测试来验证它的正确性。它为以后的开发提供支援。就算是开发后期,我们也可以轻松的增加功能或更改程序结构,而不用担心这个过程中会破坏重要的东西。而且它为代码的重构提供了保障。这样,我们就可以更自由的对程序进行改进。

2、它是一种设计行为。
编写单元测试将使我们从调用者观察、思考。特别是先写测试(test-first),迫使我们把程序设计成易于调用和可测试的,即迫使我们解除软件中的耦合。

3、它是一种编写文档的行为。
单元测试是一种无价的文档,它是展示函数或类如何使用的最佳文档。这份文档是可编译、可运行的,并且它保持最新,永远与代码同步。

4、它具有回归性。
自动化的单元测试避免了代码出现回归,编写完成之后,可以随时随地的快速运行测试。

1.3 如何更好的进行单元测试

在讨论如何更好的进行单元测试之前,先来看看我们以前是怎么测试代码的。

以前是这样测试程序的:

public int add(int x,int y) {
return x + y;
} public static void main(String args[]) {
int z = new Junit().add(2, 3);
System.out.println(z);
}

如上面所示,在测试我们写好的一个方法时,通常是用一个main方法调用一下我们要测试的方法,然后将结果打印一下。现在看来这种方式已经非常out了,所以出现了很多单元测试的工具,如:JUnit、TestNG等。借助它们可以让我们的单元测试变得非常方便、高效。今天就说说如何利用JUnit进行单元测试。

2 Junit4进行单元测试

用Junit进行单元测试很方便,尤其是Junit4引入了很多Annotation注解以后。

在测试之前首先要引入junit-4.10.jar

看测试的示例:

2.1 准备待测试类

package junit;

public class Calculator {

    public int add(int a, int b) {
return a + b;
} public int minus(int a, int b) {
return a - b;
} public int square(int n) {
return n * n;
} //Bug : 死循环
public void squareRoot(int n) {
for(; ;)
;
} public int multiply(int a, int b) {
return a * b;
} public int divide(int a, int b) throws Exception {
if (0 == b) {
throw new Exception("除数不能为零");
}
return a / b;
}
}

2.2 建立单元测试类

建好以后在该项目的“src”目录上右击,选择new——》JUnit Test Case,然后按下图填写必要信息:

说明:

1——填写单元测试类所在的包路径

2——填写测试类名称

3——会预先创建一些类,具体功能不清楚

4——选择要测试的类

点击“Next”,选择测试类中待测试的方法:

点击“Finish”即可。剩下的步骤就是填充测试方法。上面只是说明测试类的构造过程。

使用上面的方法,建立CalculatorTest测试类:

package junit;

import junit.framework.Assert;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test; public class CalculatorTest { private Calculator cal = new Calculator(); @BeforeClass // 注意,这里必须是static...因为方法将在类被装载的时候就被调用(那时候还没创建实例)
public static void before()
{
System.out.println("global");
} @AfterClass
public static void after() {
System.out.println("global destroy");
} @Before
public void setUp() throws Exception {
System.out.println("一个测试开始。。");
} @After
public void tearDown() throws Exception {
System.out.println("一个测试结束");
} @Test
@Ignore
public void testAdd() {
int result = cal.add(1, 2);
Assert.assertEquals(3, result);
} @Test
public void testMinus() {
int result = cal.minus(5, 2);
Assert.assertEquals(3, result);
} @Test
public void testMultiply() {
int result = cal.multiply(4, 2);
Assert.assertEquals(8, result);
} @Test(timeout = 1000) // 单位为毫秒
public void testSquareRoot() {
cal.squareRoot(4);
} @Test(expected = Exception.class)
public void testDivide() throws Exception {
System.out.println("teddd");
cal.divide(4, 0);// 很简单的办法.......
} // @Test
// public void testDivide() {
// int result = 0;
// try {
// result = cal.divide(10, 5);
// } catch (Exception e) {
// e.printStackTrace();
// Assert.fail();// 如果这行没有执行。说明这部分正确。
// }
// Assert.assertEquals(2, result);
// }
}

在Eclipse里Run As -> JUnit Test,运行测试类,Eclipse的JUnit的View显示如:

可以看到,CalculatorTest类中总共有5个测试用例,ignore了一个,3个测试用例通过,testSquareRoot测试不通过(因为超时),所以整个的测试结果飘红了。同时,控制台的输出结果为:

2.3 注解说明

各种注解的说明:

@Test:
表明该方法是一个测试方法 @BeforeClass 和 @AfterClass:
测试用例初始化时执行 @BeforeClass方法,当所有测试执行完毕之后,执行@AfterClass进行收尾工作。标注、@BeforeClass 和 @AfterClass的方法必须是static的,因为方法将在类被装载的时候就被调用,那时候还没创建测试对象实例。 @Before:
使用了该元数据的方法在每个测试方法执行之前都要执行一次。
@After:
使用了该元数据的方法在每个测试方法执行之后要执行一次。 @Test(expected=*.class) :
通过@Test元数据中的expected属性验证是否抛出期望的异常,expected属性的值是一个异常的类型,如果抛出了期望的异常,则测试通过,否则不通过。 @Test(timeout=xxx):
该元数据传入了一个时间(毫秒)给测试方法,如果测试方法在制定的时间之内没有运行完,则测试也失败。 @Ignore:
该元数据标记的测试方法在测试中会被忽略。同时可以为该标签传递一个String的参数,来表明为什么会忽略这个测试方法。比如:@lgnore("该方法还没有实现"),在执行的时候,仅会报告该方法没有实现,而不会运行测试方法。 在test方法内除了使用Assert的assertEquals()方法外,还能使用assertFalse()、assertTrue()、assertNull()、assertNotNull()、assertSame()、assertNotSame()等断言函数。而且如果使用的是Junit4,结合Hamcrest,使用
assertThat([value], [matcher statement])方法可以实现更灵活的断言判断(前提是引入hamcrest的jar包)。
例如:
// is匹配符表明如果前面待测的object等于后面给出的object,则测试通过
assertThat( testedObj, is( object) ); // containsString匹配符表明如果测试的字符串包含指定的子字符串则测试通过
assertThat( testedString, containsString( "developerWorks" ) ); // greaterThan匹配符表明如果所测试的数值testedNumber大于16.0则测试通过
assertThat( testedNumber, greaterThan(16.0) ); // closeTo匹配符表明如果所测试的浮点型数testedDouble在20.±.5范围之内则测试通过
assertThat( testedDouble, closeTo( 20.0, 0.5 ) ); //hasItem匹配符表明被测的迭代对象含有元素element项则测试通过assertThat(iterableObject, hasItem (element));

引入hamcrest-core-1.3.jar和hamcrest-library-1.3.jar

常用hamcrest断言的使用说明:

数值类型
//n大于1并且小于15,则测试通过
assertThat( n, allOf( greaterThan(1), lessThan(15) ) );
//n大于16或小于8,则测试通过
assertThat( n, anyOf( greaterThan(16), lessThan(8) ) );
//n为任何值,都测试通过
assertThat( n, anything() );
//d与3.0的差在±0.3之间,则测试通过
assertThat( d, closeTo( 3.0, 0.3 ) );
//d大于等于5.0,则测试通过
assertThat( d, greaterThanOrEqualTo (5.0) );
//d小于等于16.0,则测试通过
assertThat( d, lessThanOrEqualTo (16.0) ); 字符类型
//str的值为“tgb”,则测试通过
assertThat( str, is( "tgb" ) );
//str的值不是“tgb”,则测试通过
assertThat( str, not( "tgb" ) );
//str的值包含“tgb”,则测试通过
assertThat( str, containsString( "tgb" ) );
//str以“tgb”结尾,则测试通过
assertThat( str, endsWith("tgb" ) );
//str以“tgb”开头,则测试通过
assertThat( str, startsWith( "tgb" ) );
//str忽略大小写后,值为“tgb”,则测试通过
assertThat( str, equalToIgnoringCase( "tgb" ) );
//str忽略空格后,值为“tgb”,则测试通过
assertThat( str, equalToIgnoringWhiteSpace( "tgb" ) );
//n与nExpected相等,则测试通过(对象之间)
assertThat( n, equalTo( nExpected ) ); collection类型
//map中包含key和value为“tgb”的键值对,则测试通过
assertThat( map, hasEntry( "tgb", "tgb" ) );
//list中包含“tgb”元素,则测试通过
assertThat( iterable, hasItem ( "tgb" ) );
//map中包含key为“tgb”的元素,则测试通过
assertThat( map, hasKey ( "tgb" ) );
//map中包含value为“tgb”的元素,则测试通过
assertThat( map, hasValue ( "tgb" ) );

2.4 进阶

上面的Caculator待测试类里,现在我如果想给square方法多弄几个测试用例,按照上面的方法,我应该写好几个@Test方法来测试,或者每次测完再改一下输入的值和期望得到的值,好麻烦。JUnit提供如下的测试:

package hamcrest;

import java.util.Arrays;
import java.util.Collection; import junit.Calculator;
import junit.framework.Assert; import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters; @RunWith(Parameterized.class)
public class CalculatorTest2{ private Calculator cal = new Calculator();
private int param;
private int result; //构造函数,对变量进行初始化
//定义一个待测试的类,并且定义两个变量,一个用于存放参数,一个用于存放期待的结果。
public CalculatorTest2(int param, int result) {
this.param = param;
this.result = result;
} @Parameters
public static Collection data(){
return Arrays.asList(new Object[][]{
{2, 4},
{0, 0},
{-3, 9},
});
} @Test
public void squareTest() {
int temp = cal.square(param);
Assert.assertEquals(result, temp);
}
}

Eclipse里JUnit的运行结果显示为:

测试通过了,CalculatorTest2类里的parameter是每次的测试输入,result就是测试的结果。所有的测试输入和期望结果都在@Parameters标注的data函数的返回的Collection集合里,2的期望得到的平方结果值是4,0期望得到0,-3期望得到9。

把测试代码提交给JUnit框架后,框架如何来运行代码呢?答案就是——Runner。在JUnit中有很多个 Runner,他们负责调用测试代码,每一个Runner都有各自的特殊功能,要根据需要选择不同的Runner来运行测试代码。JUnit中有一个默认Runner,如果没有指定,那么系统自动使用默认 Runner来运行你的代码。这里参数化测试就没有再用默认的Runner了。

再看看打包测试测例子:

package hamcrest;

import junit.CalculatorTest;

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

测试结果:

这个测试类包含了上面的CalculatorTest.class和CalculatorTest2.class里面所有的测试函数,它的目的就是进行打包所有的测试。

断言

所有的断言都包含在Assert类中

public class Assert extends java.lang.Object

这个类提供了很多有用的断言方法来编写测试用例。只有失败的断言才会被记录。Assert 类中的一些有用的方法列式如下:

1 void assertEquals(boolean expected, boolean actual)
检查两个变量或者等式是否平衡
2 void assertTrue(boolean expected, boolean actual)
检查条件为真
3 void assertFalse(boolean condition)
检查条件为假
4 void assertNotNull(Object object)
检查对象不为空
5 void assertNull(Object object)
检查对象为空
6 void assertSame(boolean condition)
assertSame() 方法检查两个相关对象是否指向同一个对象
7 void assertNotSame(boolean condition)
assertNotSame() 方法检查两个相关对象是否不指向同一个对象
8 void assertArrayEquals(expectedArray, resultArray)
assertArrayEquals() 方法检查两个数组是否相等

(转)用JUnit4进行单元测试的更多相关文章

  1. J2EE 第二阶段项目之JUnit4进行单元测试(五)

    今天学习了JUnit4进行单元测试.这样就可以不写页面直接进行过功能模块测试.也不是很深入的了解. JUnit4和自己写的代码可以分割开来. 首先呢准备两个jar包: 可以对mapper进行测试,当然 ...

  2. 在Eclipse中使用JUnit4进行单元测试

    在Eclipse中使用JUnit4进行单元测试(初级篇) 在Eclipse中使用JUnit4进行单元测试(中级篇) 在Eclipse中使用JUnit4进行单元测试(高级篇)

  3. 【Java】在Eclipse中使用JUnit4进行单元测试(初级篇)

    本文绝大部分内容引自这篇文章: http://www.devx.com/Java/Article/31983/0/page/1 我们在编写大型程序的时候,需要写成千上万个方法或函数,这些函数的功能可能 ...

  4. springMVC整合Junit4进行单元测试

    springMVC整合Junit4进行单元测试 标签: springMVC整合Junit4junit单元测试教程springMVC入门教程   spring(10)  版权声明:本文为博主原创文章,未 ...

  5. 在Eclipse中使用JUnit4进行单元测试(图文教程一)

    在Eclipse中使用JUnit4进行单元测试 单元测试,JUnit4. 这两个有什么关系呢?这就好比(草)单元测试和(割草机).用这个JUnit4工具去辅助我们进行测试.其实不理解这个也没关系,听多 ...

  6. 在eclipse中使用JUnit4,以及使用JUnit4进行单元测试的技巧

    一 在eclipse中使用JUnit4 首先在工程上右键,选择属性,找到Java Builder Path,添加JUnit4的lib,如下图:   在要测试的类上右键新建 Junit test cas ...

  7. Idea 使用 Junit4 进行单元测试

    目录 Idea 使用 Junit4 进行单元测试 1. Junit4 依赖安装 2. 编写测试代码 3. 生成测试类 4. 运行 Idea 使用 Junit4 进行单元测试 1. Junit4 依赖安 ...

  8. [转]在Eclipse中使用JUnit4进行单元测试(初级篇)

    首先,我们来一个傻瓜式速成教程,不要问为什么,Follow Me,先来体验一下单元测试的快感! 首先新建一个项目叫JUnit_Test,我们编写一个Calculator类,这是一个能够简单实现加减乘除 ...

  9. 在Eclipse中使用JUnit4进行单元测试(初级篇)

    首先,我们来一个傻瓜式速成教程,不要问为什么,Follow Me,先来体验一下单元测试的快感! 首先新建一个项目叫JUnit_Test,我们编写一个Calculator类,这是一个能够简单实现加减乘除 ...

随机推荐

  1. 最短路径Floyd算法【图文详解】

    Floyd算法 1.定义概览 Floyd-Warshall算法(Floyd-Warshall algorithm)是解决任意两点间的最短路径的一种算法,可以正确处理有向图或负权的最短路径问题,同时也被 ...

  2. 13.ThreadPoolExecutor线程池之submit方法

    jdk1.7.0_79  在上一篇<ThreadPoolExecutor线程池原理及其execute方法>中提到了线程池ThreadPoolExecutor的原理以及它的execute方法 ...

  3. [转]Java多线程学习(吐血超详细总结)

    转自:http://www.mamicode.com/info-detail-517008.html 本文主要讲了Java中多线程的使用方法.线程同步.线程数据传递.线程状态及相应的一些线程函数用法. ...

  4. ffmpeg参数说明

    ffmpeg.exe -i F:\慶哥\慶哥之歌.mp3 -ab 56 -ar 22050 -b 500 -r 15 -s 320x240 f:\11.flv ffmpeg -i F:\01.wmv ...

  5. Java IO学习笔记二

    Java IO学习笔记二 流的概念 在程序中所有的数据都是以流的方式进行传输或保存的,程序需要数据的时候要使用输入流读取数据,而当程序需要将一些数据保存起来的时候,就要使用输出流完成. 程序中的输入输 ...

  6. oracle创建数据库到2%不动问题

  7. 使用jQuery修改动态修改超链接

    以下是修改a元素标签的href链接和文字的代码: <script type="text/javascript" src="jquery-1.9.1.min.js&q ...

  8. Docker Machine 详解

    笔者在<Docker Machine 简介>一文中简单介绍了 Docker Machine 及其基本用法,但是忽略的细节实在是太多了.比如 Docker 与 Docker Machine ...

  9. 【解决】使用compass watch xxx.scss 失败

    原始日期:2016-01-25 16:49 在上一篇博客,我们终于安装好了compass,不过紧接着使用compass watch app.scss 结果失败,经过查询资料,是compass的版本问题 ...

  10. Bash On Windows的学习

    Bash On Windows的学习 Bash On Windows的卸载 删除软件和设置:在 cmd 运行lxrun /uninstall 删除所有文件:在cmd中运行lxrun /uninstal ...