推荐一个新手学习Junit4的博客地址:http://www.cnblogs.com/eggbucket/archive/2012/02/02/2335697.html 点击打开链接
一、单元测试
单元测试概念:所谓MT(MK23),又称为模块测试,是指对软件中的最小可测试单元进行检查和验证。对于面向对象编程,最小单元就是方法。单元测试的目标是隔离程序部件并证明这些单个部件是正确的。
单元测试的优点:
1. 它是一种验证行为。
程序中的每一项功能都是测试来验证它的正确性。它为以后的开发提供支援。就算是开发后期,我们也可以轻松的增加功能或更改程序结构,而不用担心这个过程中会破坏重要的东西。而且它为代码的重构提供了保障。这样,我们就可以更*的对程序进行改进。
2. 它是一种设计行为。
编写单元测试将使我们从调用者观察、思考。特别是先写测试(test-first),迫使我们把程序设计成易于调用和可测试的,即迫使我们解除软件中的耦合。
3. 它是一种编写文档的行为。
单元测试是一种无价的文档,它是展示函数或类如何使用的最佳文档。这份文档是可编译、可运行的,并且它保持最新,永远与代码同步。
4. 它具有回归性。
自动化的单元测试可以在代码编写完成之后,随时随地的快速运行测试。注:回归测试是指修改了旧代码后,重新进行测试以确认修改没有引入新的错误或导致其他代码产生错误。自动回归测试将大幅降低系统测试、维护升级等阶段的成本。
二、Junit
Junit简介
JUnit是一个开源的java单元测试框架。在1997年,由 Erich Gamma 和 Kent Beck 开发完成。用于编写和运行可重复的测试,是单元测试框架体系xUnit的一个实例(用于java语言)。它包括如下特性:
① 用于测试期望结果的断言(Assertion)
② 用于共享共同测试数据的测试工具
③ 用于方便的组织和运行测试的测试套件
④ 图形和文本的测试运行器
目前主要版本有3.X和4.X,差异很大,后文中会有介绍。
Junit3使用基本流程
① 写测试类并继承TestCase类;
② 写测试方法testXXXX();
③ 写测试套件类将test case加入test suite;
④ 运行TestRunner进行测试;
重要概念
TestCase(测试用例)
当一个类继承junit的TestCase类,即成为一个测试类,它以testXXXX的形式包含测试方法(public void修饰)。
TestSuite(测试集合或测试套件)
一组测试,将相关的测试归入一组,将多个测试方法归入一个test suite,例如上例中的缺省test suite默认会扫描测试类中的所有测试方法并归入测试集合。如果没有为test case写test suite的话,系统会默认为每个test case生成一个test suite。
TestRunner(测试运行器)
用于执行TestSuite,继承抽象类BaseTestRunner。可以使用BaseTestRunner的子类junit.awtui.TestRunner、junit.swingui.TestRunner、junit.textui.TestRunner
来运行测试集合。
Assert(断言)
用于检测条件是否成立,当条件成立则Assert方法通过,否则抛出异常。例如Assert.assertEquals(3, result);判断result是否跟期望的3相等,如果相等则通过,否则测试失败。
Junit3实例使用
需要测试类:
package com.user.junit;Junit3测试程序:
public class SampleCalculator {
public int add(int add1, int add2) {
return add1 + add2;
}
public int subtration(int sub1, int sub2) {
return sub1 - sub2;
}
public int multiplication(int mul1, int mul2) {
return mul1 * mul2;
}
public int division(int div1, int div2) {
return div1 / div2;
}
}
package com.user.junit;3种测试运行器及Eclipse运行结果对比:
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
public class Junit3TestSample extends TestCase { //继承TestCase类
SampleCalculator sc;
public Junit3TestSample(){}
//用于将单个方法加入suite
public Junit3TestSample(String methodName){
super(methodName);
}
//每次在测试方法之前,此方法都会被执行,可以做一些准备工作
public void setUp(){
sc = new SampleCalculator();
}
//每次在测试方法之后,此方法都会被执行,可以做一些善后工作
public void tearDown(){
System.out.println("case end");
}
//将testcase加入testsuite
public static Test suite(){
TestSuite suite = new TestSuite();
suite.addTestSuite(Junit3TestSample.class);//添加类中所有方法
//suite.addTest(new Junit3TestSample("testAdd"));//添加类中某个方法
return suite;
}
//测试方法必须以test开头
public void testAdd() {
assertEquals(7, sc.add(3, 4));
assertTrue(true);
}
public void testSub() {
assertEquals(5, sc.subtration(10, 5));
}
public void testMul() {
assertEquals(6, sc.multiplication(2, 3));
}
public void testDiv() {
assertEquals(2, sc.division(4, 2));
}
public void testDiv1() {
try{
assertEquals(5, sc.division(5, 0));
}catch(ArithmeticException ae){
assertTrue(true);
}
}
//使用TestRunner运行测试套件,IDE如Eclipse中不需要
public static void main(String[] args) {
//swing,awt,text三种测试运行器
// junit.awtui.TestRunner.run(Junit3TestSample.class);
junit.swingui.TestRunner.run(Junit3TestSample.class);
// junit.textui.TestRunner.run(suite());
}
}
用Junit4实现Junit3的测试
package com.user.junit;Junit4与junit3对比
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.*;//必须要添加,才能使用各种断言
public class Junit4TestSample {
SampleCalculator sc;
@BeforeClass
public static void beforeClass(){
System.out.println("BeforeClass is called");
}
@AfterClass
public static void afterClass(){
System.out.println("AfterClass is called");
}
@Before
public void before() {
sc = new SampleCalculator();
}
@After
public void after(){
System.out.println("case end");
}
@Test
public void testAdd() {
assertEquals(7, sc.add(3, 4));
}
@Test
public void testSub() {
assertEquals(5, sc.subtration(10, 5));
}
@Test
public void testMul() {assertEquals(6, sc.multiplication(2, 3));}
@Test
public void testDiv() {
assertEquals(2, sc.division(4, 2));
}
@Test(expected=ArithmeticException.class)
public void testDiv1() {
assertEquals(5, sc.division(5, 0));
}
}
Junit 4.x 利用了 java 5 的特性( annotation )的优势,使得测试比起 3.x 版本 更加的方便简单, junit 4.x 不是旧版本的简单升级,它不是旧版本的简单升级,整个框架的包结构已经彻底改变,但 4.x 版本 仍然能够很好的兼容旧版本测试套件。
Junit4与junit3对比:
Junit4常用注解
① @Before:初始化方法,在任何一个测试执行之前必须执行的代码。 跟3.X中的setUp()方法具有相同功能。
② @After:释放资源,在任何测试执行之后需要进行的收尾工作。跟3.x中的 tearDown ()方法具有相同功能。
③ @Test:测试方法,表明这是一个测试方法。在Junit中将会自动被执行。对于方法的声明也有如下要求:名字可以随便取,没有任何限制,但是返回值必须为void,而且不能有任何参数。如果违反这些规定,会在运行时抛出一个异常。该annotation可以测试期望异常和超时时间,如@Test(timeout = 100),我们给测试函数设定一个执行时间,超过了这个时间(100毫秒),它们就会被系统强行终止,并且系统还会向你汇报该函数结束的原因是因为超时,这样你就可以发现这些Bug了。同时还可以测试期望的异常,例如:@Test(expected=IllegalArgumentException.class)
④ @Ignore:忽略的测试方法,标注的含义就是“某些方法尚未完成,暂不参与此次测试”;这样的话测试结果就会提示你有几个测试被忽略,而不是失败。一旦你完成了相应函数,只需要把@Ignore标注删去,就可以进行正常的测试。
⑤ @BeforeClass:针对所有测试,在所有测试方法执行前执行一次,且必须为public static void;此annotataion为4.x新增功能。
⑥ @AfterClass:针对所有测试,在所有测试方法执行结束后执行一次,且必须为public static void;此annotataion为4.x新增功能。
Junit4对junit3测试套件的支持
JUnit4中最显著的特性是没有套件。为了替代老版本的套件测试,套件被两个新注释代替:@RunWith、@SuteClasses。通过@RunWith指定一个特殊的运行器:Suite.class套件运行器,并通过@SuiteClasses注释,将需要进行测试的类列表作为参数传入。
编写流程如下:
① 创建一个空类作为测试套件的入口;
② 将@RunWith、@SuiteClasses注释修饰这个空类;
③ 把Suite.class作为参数传入@RunWith注释,以提示JUnit将此类指定为运行器;
④ 将需要测试的类组成数组作为@SuiteClasses的参数。
如下例:
package com.user.junit;由此可见junit4是可以兼容junit3运行的,故在旧系统中可以直接升级 为junit4。
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class)
@SuiteClasses(Junit3TestSample.class)
public class Junit4suite {
}
Junit编程规范
① 单元测试代码应位于单独的Source Folder下。
② 测试类应跟被测试类位于统一package下
③ 选择有意义的测试方法名
④ 保持测试的独立性
⑤ 为暂未实现的测试代码抛出失败(fail)或忽略(ignore)
⑥ 在调用断言(assert)方法时给出失败的原因
三、Jmock
Jmock简介
JUnit可以轻松的完成关联依赖关系少或者比较简单的类的单元测试,但是对于关联到其它比较复杂的类或对运行环境有要求的类的单元测试,模拟环境或者配置环境会非常耗时,实施单元测试比较困难。而jmock通过mock对象来模拟一个对象的行为,从而隔离开我们不关心的其他对象,使得测试变得简单。模拟对象(Mock Object)可以取代真实对象的位置,用于测试一些与真实对象进行交互或依赖于真实对象的功能,模拟对象的背后目的就是创建一个轻量级的、可控制的对象来代替测试中需要的真实对象,模拟真实对象的行为和功能。
mock对象使用范畴:
① 真实对象具有不可确定的行为,产生不可预测的效果。
② 真实对象很难被创建的。
③ 真实对象的某些行为很难被触发。
④ 真实对象实际上还不存在的。
Jmock实例使用
需要测试类:
package com.user.junit;接口:
public class UserManager {
public AddressService addressService;
public int getCityCode(String userName){
int cityCode=0;
if("nanjing".equals(addressService.findAddress(userName)))
cityCode = 1;
return cityCode;
}
}
package com.user.junit;类:
public interface AddressService {
public String findAddress(String userName);
}
package com.user.junit;测试程序:
public class AddressServiceClass {
public String findAddress(String userName){return userName;}}
package com.user.junit;#在模拟类时需要指定setImposteriser(ClassImposteriser.INSTANCE),如上所示。
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.junit.Test;
import org.jmock.lib.legacy.ClassImposteriser;
import static org.junit.Assert.*;
public class UserManagerTest {
// 建立一个test上下文对象。
// 需要模拟的是接口
Mockery context = new Mockery();
// 需要模拟的是类
// Mockery context = new Mockery() {
// {setImposteriser(ClassImposteriser.INSTANCE);
// } };
// 生成一个mock对象
final AddressService addressServcie = context.mock(AddressService.class);
// final AddressServiceClass addressServcie = context
// .mock(AddressServiceClass.class);
@Test
public void test1() {
context.checking(new Expectations() {
{
oneOf(addressServcie).findAddress("yld");
will(returnValue("nanjing"));
}
});
UserManager um = new UserManager();
um.addressService = addressServcie;
assertEquals(1, um.getCityCode("yld"));
}
}
那么这里做了什么事情呢?
① 首先,我们建立一个test上下文对象context。
② 用这个mockery context建立了一个mock对象来mock AddressService.
③ 设置期待。
④ 生成UserManager对象,设置addressService,调用findAddress。
⑤ 验证期望被满足。
基本上,一个简单的jmock应用大致就是这样一个流程。
Jmock中的各种概念
期望:设置在某种情况下mock对象的action。
基本的期望框架是:
invocation-count (mock-object).method(argument-constraints);invocation-count:调用的次数约束 ,mock-object mock:对象,method:方法, argument-constraints 参数约束,will(action): 方法触发的动作。
will(action);
invocation-count可以指定为如下:exactly 精确多少次 ,oneOf 精确1次, atLeast 至少多少次 ,between 一个范围 ,atMost 至多多少次 ,allowing 任意次 ,ignoring 忽略 ,never 从不执行。
argument-constraints除了直接指定参数外,还可以如下指定:any,anything接收任意值,aNull接收null,aNonNull接收非null,IsNot,AnyOf等。
will(action)可以返回值,也可以抛出异常。返回值:will(returnValue(xx)), 抛出异常:will(throwException(xx))。除了以上两个常见的action外,还可以如下:
VoidAction;
ReturnEnumerationAction 返回Enumeration
DoAllAction 所有的Action都执行,但是只返回最后一个Action的结果。
ActionSequence 每次调用返回其Actions列表中的下一个Action的结果。
CustomAction 一个抽象的Action,方便自定义Action。
除了以上最基本的期待框架外,jmock期待框架中还可以包括状态机,状态机的作用在于模拟对象在什么状态下调用才用触发。框架如下:
invocation-count (mock-object).method(argument-constraints);inSequence 顺序 ,when 当mockery的状态为指定的时候触发,then 方法触发后设置mockery的状态。
inSequence(sequence-name);
when(state-machine.is(state-name));
will(action);
then(state-machine.is(new-state-name));
四、Mockito
Jmock需要在执行前记录期望行为(expectations),显得很繁琐,而Mockito通过在执行后校验哪些函数已经被调用,消除了对期望行为(expectations)的需要,API非常简洁。
Mockito测试程序:(待测试程序同Jmock)
package com.user.junit;Mockito简单运用说明
import org.junit.Test;
import org.mockito.Mockito;
import static org.mockito.Mockito.*;
import static org.junit.Assert.*;
public class UserManagerMockitoTest {
@Test
public void testGetCityCode(){
AddressService as = mock(AddressService.class);
Mockito.when(as.findAddress("yld")).thenReturn("nanjing");
UserManager um = new UserManager();
um.addressService = as;
assertEquals(1,um.getCityCode("yld"));
verify(as,times(1)).findAddress("yld");
}
① when(mock.someMethod()).thenReturn(value):设定mock对象某个方法调用时的返回值。可以连续设定返回值,即when(mock.someMethod()).thenReturn(value1).then
Return(value2),第一次调用时返回value1,第二次返回value2。也可以表示为如下:
when(mock.someMethod()).thenReturn(value1,value2)。
② 调用以上方法时抛出异常: when(mock.someMethod()).thenThrow(new Runtime
Exception());
③ 另一种stubbing语法:
doReturn(value).when(mock.someMethod())
doThrow(new RuntimeException()).when(mock.someMethod())
④ 对void方法进行方法预期设定只能用如下语法:
doNothing().when(mock.someMethod())
doThrow(new RuntimeException()).when(mock.someMethod())
doNothing().doThrow(new RuntimeException()).when(mock.someMethod())
⑤ 方法的参数可以使用参数模拟器,可以将anyInt()传入任何参数为int的方法,即anyInt匹配任何int类型的参数,anyString()匹配任何字符串,anySet()匹配任何Set。
⑥ Mock对象只能调用stubbed方法,调用不了它真实的方法,但是Mockito可以用spy来监控一个真实对象,这样既可以stubbing这个对象的方法让它返回我们的期望值,又可以使得对其他方法调用时将会调用它的真实方法。
⑦ Mockito会自动记录自己的交互行为,可以用verify(…).methodXxx(…)语法来验证方法Xxx是否按照预期进行了调用。
(1) 验证调用次数:verify(mock,times(n)).someMethod(argument),n为被调用的次数,如果超过或少于n都算失败。除了times(n),还有never(),atLease(n),atMost(n)。
(2) 验证超时:verify(mock, timeout(100)).someMethod();
(3) 同时验证:verify(mock, timeout(100).times(1)).someMethod();
Mockito实例使用
待测试代码:
package com.user.junit;测试代码:
public class MockitoClass {
public int method(){
int rad=(int) (Math.random()*100);
if(rad == 1)
return 1;
else return 0;
}
public void void_method(){
System.out.println("void_method");
}
public int matcher_method(int i){
return i;
}
public int spy_method(int i){
return i;
}
}
package com.user.junit;五、PowerMock
import static org.mockito.Mockito.*;
import static org.junit.Assert.*;
import org.junit.Test;
import org.mockito.Mockito;
public class MockitoClassTest {
@Test
public void testMethod1(){
MockitoClass mc = mock(MockitoClass.class);
//when(mc.method()).thenReturn(1);
doReturn(1).when(mc).method();
assertEquals(1,mc.method());
verify(mc,times(1)).method();
}
@Test(expected = RuntimeException.class)
public void testMethod2(){
MockitoClass mc = mock(MockitoClass.class);
//when(mc.method()).thenThrow(new RuntimeException());
doThrow(new RuntimeException()).when(mc).method();
mc.method();
}
@Test
public void testVoid_method(){
MockitoClass mc = mock(MockitoClass.class);
Mockito.doNothing().when(mc).void_method();
mc.void_method();
verify(mc,times(1)).void_method();
}
@Test(expected = RuntimeException.class)
public void testVoid_method1(){
MockitoClass mc = mock(MockitoClass.class);
Mockito.doThrow(new RuntimeException()).when(mc).void_method();
mc.void_method();
}
@Test
public void testMatcher_method(){
MockitoClass mc = mock(MockitoClass.class);
when(mc.matcher_method(anyInt())).thenReturn(2);
assertEquals(2,mc.matcher_method(5));
verify(mc).matcher_method(anyInt());
}
@Test
public void testSpy_method(){
MockitoClass mc = spy(new MockitoClass());
when(mc.spy_method(anyInt())).thenReturn(2);
assertEquals(2,mc.spy_method(5));
assertEquals(5,mc.matcher_method(5));
}
}
PowerMock简介
Mockito可以完成对一般对象方法的模拟,但是对于静态函数、构造函数、私有函数等还是无能为力,同时方法执行前需要记录期望也显得很繁琐,并且需要注意的是,使用Mockito的前提是mock的对象可以替代实际的对象,如果需要mock的对象是方法内新生成的,无法从方法外部将mock的对象传递到方法内时,Mockito将起不了作用。PowerMock却可以解决上述的各种问题。
PowerMock是在Mockito的基础上做出的扩展。通过提供定制的类加载器以及一些字节码篡改技巧的应用,PowerMock 实现了对静态方法、构造方法、私有方法以及 Final 方法的模拟支持,对静态初始化过程的移除等强大的功能。因为 PowerMock 在扩展功能时完全采用和被扩展的框架相同的 API, 所以在掌握了Mockito的使用方法后, PowerMock 非常容易上手。
PowerMock实例使用
待测试程序:
package com.user.junit;测试程序:
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class PowerMockClass {
public static void void_static(){
throw new NullPointerException("void_static");}
public static int static_method(){return 1;}
public static void static_arg(int i){
throw new NullPointerException("static_arg");}
private int private_method(int i){return i;}
public final int final_method(int i){return i;}
public int method(int i){return i;}
public void new_object() throws IOException{
File file = new File("123");
try {
FileInputStream in = new FileInputStream(file);
in.close();
} catch (IOException e) {e.printStackTrace();}
}
}
package com.user.junit;PowerMock使用小结
import java.io.FileInputStream;
import java.lang.reflect.Method;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import static org.mockito.Mockito.*;
import static org.junit.Assert.*;
@RunWith(PowerMockRunner.class)
@PrepareForTest(PowerMockClass.class)
public class PowerMockClassTest {
@Test(expected = IllegalStateException.class)
public void testVoid_static() throws Exception{
PowerMockito.mockStatic(PowerMockClass.class);
PowerMockito.doThrow(new IllegalStateException()).when(PowerMockClass.class, "void_static");
PowerMockClass.void_static();
}
@Test
public void testStatic_method(){
PowerMockito.mockStatic(PowerMockClass.class);
PowerMockito.when(PowerMockClass.static_method()).thenReturn(5);
assertEquals(5,PowerMockClass.static_method());
}
@Test(expected = IllegalStateException.class)
public void testStatic_arg() throws Exception{
PowerMockito.mockStatic(PowerMockClass.class);
PowerMockito.doThrow(new IllegalStateException()).when(PowerMockClass.class, "static_arg",1);
PowerMockClass.static_arg(1);
}
@Test
public void testPrivate_method() throws Exception{
PowerMockClass power = PowerMockito.mock(PowerMockClass.class);
PowerMockito.when(power,"private_method",2).thenReturn(3);
Class<?> cls = Class.forName("com.fnst.youliangdong.junit.PowerMockClass");
Method method = cls.getDeclaredMethod("private_method", int.class);
method.setAccessible(true);
assertTrue(method.invoke(power, 2).equals(3));}
@Test
public void testFinal_method() throws Exception{
PowerMockClass power = PowerMockito.mock(PowerMockClass.class);
PowerMockito.when(power.final_method(1)).thenReturn(2);
assertEquals(2,power.final_method(1));}
@Test(expected = NullPointerException.class)
public void testNew_object() throws Exception{
PowerMockClass power = new PowerMockClass();
FileInputStream is = mock(FileInputStream.class);
PowerMockito.whenNew(FileInputStream.class).withAnyArguments().thenReturn(is);
PowerMockito.doThrow(new NullPointerException()).when(is).close();
power.new_object();}
@Test
public void testSpy() throws Exception{
PowerMockClass power = PowerMockito.spy(new PowerMockClass());
PowerMockito.when(power,"private_method",1).thenReturn(5);
Class<?> cls = Class.forName("com.fnst.youliangdong.junit.PowerMockClass");
Method method = cls.getDeclaredMethod("private_method", int.class);
method.setAccessible(true);
assertTrue(method.invoke(power, 1).equals(5));
try{power.void_static();
}catch(NullPointerException e){
assertTrue("void_static".equals(e.getMessage()));}
assertEquals(1,power.static_method());
try{power.static_arg(1);
}catch(NullPointerException e){}
}
}
① @RunWith(PowerMockRunner.class)和@PrepareForTest(PowerMockClass.Class)这两个注解一定要加,否则PowerMock无效。@PrepareForTest中需要添加被测试的类,以及被测方法中需要mock的static方法所属的类,如果有多个类要添加,则格式为
@PrepareForTest({Class1.Calss,Class2.Class})。
② PowerMock各方法的语法(一般方法与Mockito用法一致):
(1) void静态:PowerMockito.mockStatic(xxx.Class);
PowerMockito.doNothing().when(mock,”methodName”,arg1,arg2);
(2) 有返回值静态:PowerMockito.mockStatic(xxx.Class);
PowerMockito.when(mock.method()).thenReturn(value);
(3) 私有:PowerMockito.mock(xxx.Class);
PowerMockito.when(mock,”methodName”,arg1,arg2).thenReturn(value);
(4) 构造函数:PowerMockito.mock(xxx.Class);
PowerMockito.whenNew(xxx.Class).withAnyArguments().thenReturn();
③ PowerMock会对字节码篡改,即测试时的字节码与平时编译出来的字节码是不一样的,而很多统计单元测试覆盖率的插件是以字节码来统计的,所以跟PowerMock会有冲突,用PowerMock编写的测试程序不能被统计进覆盖率。
六、总结
Junit可以说是单元测试中必不可少的,因为它提供了验证的框架,使得可以直接由程序来验证结果的正确性,而不用人为的去观察结果。对于一般方法的模拟,我个人认为Mockito比Jmock的语法要更简洁、易懂。Mockito模拟不了的方法,可以通过PowerMock去模拟,PowerMock虽然很强大,但是缺点也是很易见的。所以单元测试可以使用Junit4+Mockito或者Junit4+PowerMock。
Junt、Jmock、Mockito、PowerMock不管谁优谁劣都只是实施单元测试的辅助工具,我觉得单元测试最重要的还是最根本的对方法的验证,要完善的考虑各种情形,要保证验证观点的充足,比如,写文件,不仅仅有新文件生成就可以判定成功,还要验证,文件中书写的内容是否符合预期。