JUnit 3.8.2和JUnit 4.8.1学习笔记
JUnit学习笔记 2011年10月01日 环境 MyEclipse 9.0
铺垫:
1、单元测试(Unit Test):
是针对软件的最小模块进行正确性检验的测试工作。
所谓最小模块,在OOP的范畴内,通常是指对象的方法。
2、为什么要做单元测试?
进行单元测试是降低软件风险和减少维护成本的有效途径。
3、以前是怎么做测试的?
main方法、sysout输出……
优点: 快速直接
缺点: 没有留下可重用的代码。
无法进行自动化回归测试。
增加了类的体积,代码不清晰。
不规范、不优雅。
4、断言(Assertion):
在软件开发中是一种常用的调试方式,很多开发语言中都支持这种机制。
在实现中,assertion就是在程序中的一条语句,它对一个boolean表达式进行检查,
一个正确程序必须保证这个boolean表达式的值为true;
如果该值为false,说明程序已经处于不正确的状态下,assert将给出警告或退出。
一般来说,assertion用于保证程序最基本、关键的正确性。
assertion检查通常在开发和测试时开启。为了提高性能,在软件发布后,assertion检查通常是关闭的。
/*************************************************************************************************
* 需补充关键词:
* ① 测试驱动开发(TDD: Test-Driven Development)
* ② Hamcrest(JUnit4.4后引入的测试框架提供了一套匹配符,可结合使用全新的断言语法:assertThat)
************************************************************************************************/
---------------------------------------我是华丽的分割线-------------------------------------------
正题:
一、JUnit简介
JUnit是基于面向对象构建的java单元测试框架。
JUnit是开放源代码项目,可按需要进行扩展,操作相对简单。
JUnit官方网站:http://www.junit.org
JUnit的口号:keep the bar green to keep the code clean。(PS:口号的具体含义用过JUnit后就知道了……)
目前JUnit官网最新版本是4.10。
我用的MyEclipse 9.0内置了3.8.2和4.8.1。
3.X和4.X两个版本最主要的区别是:
JUnit3基于继承
JUnit4基于注解
两种未通过测试的结果:
Failure: 测试失败
Error: 异常错误
二、JUnit应用
待测类之一:
/**
* @author Maxpin on 2011-10-01 含有四则运算方法的普通类
*/
public class Calc {
/**
* 加法
*
* @param x
* 数x
* @param y
* 数y
* @return 和
*/
public int add(int x, int y) {
return x + y;
}
/**
* 减法(私有方法)
*
* @param x
* 数x
* @param y
* 数y
* @return 差
*/
@SuppressWarnings("unused")
private int subtract(int x, int y) {
return x - y;
}
/**
* 乘法
*
* @param x
* 数x
* @param y
* 数y
* @return 积
*/
public int multiply(int x, int y) {
return x * y;
}
/**
* 除法
*
* @param x
* 数x
* @param y
* 数y
* @return 商
* @throws Exception
* 抛出异常
*/
public int divide(int x, int y) throws Exception {
if (0 == y) {
throw new Exception("除数不能为0");
}
return x / y;
}
}
1、使用JUnit 3.8.2的操作步骤及注意事项:
1)为项目导入JUnit的jar包(Build path → Add Library → JUnit)。
2)新建一个名为test的Source Folder,用于存放测试类源代码。
3)目标类与测试类应该位于同一个包下面,这样测试类中就不必导入源代码所在的包,因为他们位于同一个包下面。
4)测试类的命名规则:假如目标类是Calc,那么测试类应该命名为TestCalc或者是CalcTest。
5)测试类必须要继承于TestCase父类。
6)测试方法必须要满足public void修饰、无方法参数、方法名必须以test开头。
7)重写setUp()方法和tearDown()方法以便于对测试数据的初始化和回归操作。
8)执行顺序:setUp(测试方法之前)、testMethod(执行的测试方法)、tearDown(测试方法之后)。
9)为了测试私有方法,可以使用反射机制。
10)TestSuite(测试套件):可以将多个测试组合到一起,同时执行多个测试。
/**
*
* @author Maxpin on 2011-10-01 含有四则运算方法的测试类
*
*/
public class CalcTest extends TestCase {
private Calc calc;
@Override
protected void setUp() throws Exception {
calc = new Calc();
}
@Override
protected void tearDown() throws Exception {
System.out.println("tearDown invoked");
}
/**
* 测试加法
*/
public void testAdd() {
int result = calc.add(1, 2);
assertEquals(3, result);
System.out.println("加法测试完毕");
}
/**
* 测试减法(通过反射测试私有方法)
*/
public void testSubtract() {
Class<Calc> clazz = Calc.class;
try {
Method method = clazz.getDeclaredMethod("subtract", new Class[] {
Integer.TYPE, Integer.TYPE });
// 关闭安全检查
method.setAccessible(true);
Object result = method.invoke(calc, new Object[] { 2, 3 });
assertEquals(-1, result);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("减法测试完毕");
}
/**
* 测试除法
*/
public void testDivide() {
Throwable th = null;
int result = 0;
try {
result = calc.divide(10, 5);
} catch (Exception e) {
th = e;
fail("测试失败");
}
assertNull(th);
assertEquals(2, result);
System.out.println("除法测试完毕");
}
/**
* 测试除法(期望报异常)
*/
public void testDivideByZero() {
Throwable th = null;
try {
@SuppressWarnings("unused")
int result = calc.divide(10, 0);
fail("测试失败");
} catch (Exception e) {
th = e;
}
assertNotNull(th);
assertEquals(Exception.class, th.getClass());
assertEquals("除数不能为0", th.getMessage());
System.out.println("除法异常测试完毕");
}
}
/**
*
* @author Maxpin on 2011-10-01 删除目录方法的测试类
*
*/
public class DeleteAllTest extends TestCase {
public DeleteAllTest() {
}
/**
* 用于重复测试指定方法、指定次数时使用的构造方法
*
* @param name
* 方法名
*/
public DeleteAllTest(String name) {
super(name);
}
/**
* 测试删除目录的方法
* 构造测试数据,目录结构:
* ---dir---
* / | \
* / | \
* / | \
* / | \
* subFile1 subFile2 file1.txt
* /
* /
* file2.txt
*/
public void testDeleteAll() {
File directory = null;
try {
//主文件夹
directory = new File("dir");
directory.mkdir();
//子文件夹1
File subFile1 = new File(directory, "subFile1");
//子文件夹2
File subFile2 = new File(directory, "subFile2");
subFile1.mkdir();
subFile2.mkdir();
//主文件夹下的文件
File file1 = new File(directory, "file1.txt");
file1.createNewFile();
//子文件夹1下的子文件
File file2 = new File(subFile1, "file2.txt");
file2.createNewFile();
DeleteAll.deleteAll(directory);
} catch (Exception ex) {
Assert.fail();
}
Assert.assertNotNull(directory);
String[] names = directory.list();
Assert.assertEquals(0, names.length);
directory.delete();
System.out.println("I/O流删除目录测试完毕");
}
}
/**
*
* @author Maxpin on 2011-10-01 测试套件类
*
*/
public class TestAll extends TestCase {
/**
* 定义测试集
*
* @return
*/
public static Test suite() {
TestSuite suite = new TestSuite();
// 四则运算
suite.addTestSuite(CalcTest.class);
// 只乘法
suite.addTestSuite(CalcTest2.class);
// 删除目录
suite.addTest(new RepeatedTest(new DeleteAllTest("testDeleteAll"), 20));
return suite;
}
}
2、使用JUnit 4.8.1的操作步骤及注意事项:
1)为项目导入JUnit的jar包(Build path → Add Library → JUnit)。
2)新建一个名为test的Source Folder,用于存放测试类源代码。
3)目标类与测试类应该位于同一个包下面,这样测试类中就不必导入源代码所在的包,因为他们位于同一个包下面。
4)JUnit4并不要求测试类继承TestCase父类。
5)在一个测试类中,所有被 @Test 注解所修饰的public void方法都是TestCase(测试用例),可以被JUnit所执行。
6)规范:虽然JUnit 4并不要求测试方法名以test开头,
但最好还是按照JUnit 3.8.2的要求那样,以test作为测试方法名的开头。
7)在JUnit4中,通过 @Before 注解实现与JUnit 3.8.2中的setUp方法同样的功能,
通过 @After 注解实现与JUnit 3.8中的tearDown方法同样的功能。
8)在JUnit4中,可以使用 @BeforeClass与 @AfterClass注解修饰一个public static void no-arg 的方法,
这样被 @BeforeClass注解所修饰的方法会在所有测试方法执行前执行;
被 @AfterClass注解所修饰的方法会在所有测试方法执行之后执行。
9)注解 @Ignore可用于修饰测试类与测试方法,当修饰测试类时,表示忽略掉类中的所有测试方法;
当修饰测试方法时,表示忽略掉该测试方法。
10)参数化测试(Parameters):当一个测试类使用参数化运行器运行时,
需要在类的声明处加上 @RunWith(Parameterized.class)注解,表示该类将不使用JUnit内建的运行器运行,
而使用参数化运行器运行;在参数化运行类中提供参数的方法上要使用 @Parameters注解来修饰,
同时在测试类的构造方法中为各个参数赋值(构造方法是由JUnit 调用的),
最后编写测试类,它会根据参数的组数来运行测试多次。
11)在JUnit4中,如果想要同时运行多个测试,需要使用两个注解:
@RunWith(Suite.class)以及 @Suite.SuiteClasses(),通过这两个注解分别指定使用Suite运行器来运行测试,
以及指定了运行哪些测试类,其中的 @SuiteClasses中可以继续指定Suite,这样JUnit会再去寻找里面的测试类,
一直找到能够执行的TestCase并执行之。
12)JUnit4中可以使用JUnit3的方法进行测试,而不使用注解机制,即JUnit3可以完美升级至JUnit4,但4更灵活、方便。
/**
* @author Maxpin on 2011-10-01 含有四则运算方法的测试类
*
*/
public class CalcTest {
private Calc calc;
//在所有测试方法前执行一次
@BeforeClass
public static void globalInit() {
System.out.println("globalInit invoked");
}
//在所有测试方法后执行一次
@AfterClass
public static void globalDestroy() {
System.out.println("globalDestroy invoked");
}
//在每个测试方法前执行
@Before
public void setUp() {
System.out.println("Before");
calc = new Calc();
}
//在每个测试方法后执行
@After
public void tearDown() {
System.out.println("After");
}
// 测试方法执行超过1000毫秒后算超时,测试将失败
@Test(timeout = 1000)
public void testAdd() {
calc = new Calc();
int result = calc.add(1, 2);
assertEquals(3, result);
}
// 测试方法期望得到的异常类
@Test(expected = Exception.class)
public void testDivide() throws Exception {
calc.divide(1, 0);
}
// 执行测试时将忽略掉此方法,如果用于修饰类,则忽略整个类
@Ignore("not ready yet")
@Test
public void testSubtract() {
calc = new Calc();
int result = calc.subtract(1, 2);
assertEquals(-1, result);
}
}
/**
* @author Maxpin on 2011-10-01 多组参数值测试加法运算的测试类
*
* 使用参数化运行器运行
*/
@RunWith(Parameterized.class)
public class CalcParametersTest {
Calc clac;
private int expected; // 期望值
private int input1; // 输入值1
private int input2; // 输入值2
@SuppressWarnings("rawtypes")
// 提供参数的方法
@Parameters
public static Collection preparedData() {
Object[][] obj = { { 3, 1, 2 }, { 0, -5, 5 }, { 10, 8, 2 } };
return Arrays.asList(obj);
}
/**
* 带参构造方法
*
* @param expected
* 参数期望值
* @param input1
* 参数输入值1
* @param input2
* 参数输入值2
*/
public CalcParametersTest(int expected, int input1, int input2) {
this.expected = expected;
this.input1 = input1;
this.input2 = input2;
}
/**
* @throws java.lang.Exception
*/
@Before
public void setUp() throws Exception {
clac = new Calc();
}
/**
* Test method for {@link com.accp.junit4.Calc#add(int, int)}.
*/
@Test
public void testAdd() {
// 直接断言,会根据参数的组数来执行测试多次
assertEquals(this.expected, clac.add(this.input1, this.input2));
}
}
/**
*
* @author Maxpin on 2011-10-01 套件测试类
*
* 测试套件
*
* 使用Suite运行器测试,并指定测试类
*/
@RunWith(Suite.class)
@Suite.SuiteClasses({ CalcTest.class, CalcParametersTest.class })
public class TestAll {
}
/**
*
* @author Maxpin on 2011-10-01 套件测试类
*
* 测试套件
*
* 使用Suite运行器测试也可以嵌套Suite
*/
@RunWith(Suite.class)
@Suite.SuiteClasses({ TestAll.class, LargestorTest.class })
public class TestAllTest {
}
---------------------------------------我是华丽的分割线-------------------------------------------
总结:
JUnit单元测试框架体积小巧,功能强大。
及时测试显然是一种良好的开发习惯,即便测试代码量多过于被测代码量。。。
JUnit3和JUnit4相比较而言,后者更加灵活便利。
测试代码执行前后,被测代码的状态始终应保持一致性。
引用某名言:单元测试不是为了证明您是对的,而是为了证明您没有错误。
最后:附上完整版的练习实例源码,仅供学习参考交流使用。