Mockito是一个开源mock框架,官网:http://mockito.org/,源码:https://github.com/mockito/mockito
Junit是一个Java语言的单元测试框架,官网:http://junit.org/
这两个jar包的下载地址是:http://download.csdn.net/detail/bgk083/9043363
单元测试(unit testing) ,是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如C语言中单元指一个函数,Java里单元指一个类,图形化的软件中可以指一个窗口或一个菜单等。总的来说,单元就是人为规定的最小的被测功能模块。单元测试是在软件开发过程中要进行的最低级别的测试活动,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。
这里有几个关键点: ①单元是人为规定的 ②单元测试是独立单元,要和其他部分相分离。
单元测试的作用?(参考http://blog.csdn.net/sunliduan/article/details/42026509)
1. 提高代码质量
----实现功能
---- 逻辑严密
稍有信息素质的专业程序员总是追求着一件事情 --- 写出优雅的代码 。这里的优雅,不仅仅是指需求功能的准确实现,更是系统上线后的稳定和高性能。而测试用例的认真思考与书写,就给了程序员一个“深思熟虑”的机会,让我们在“做”之前先“想”好了 。当然,这可能需要丰富的编程经验。不过我也相信,经验是一点点积累来的,所以从现在开始,为时不晚。
2. 减少调试时间
我们以前的测试,基本上都是从 web 层开始,一条线的测试。首先这种测试需要我们打包部署后运行整个程序来执行,耗费时间较多;其次也是最重要的,出现错误后我们不能很快的定位是那一层的问题,只有一步一步的断点调试,方可定位到错误,这样调试的时间是很长的。
而在 Java 中的单元测试,一般是对一个类的测试。而这个恰恰让 coder 极为迅速并且准确的定位错误的来源 --- 就是本类!因此,极大的减少了我们调试的时间。
3. 隔离测试
在一个大项目或者关系比较紧密的项目中,很有可能出现两个子系统之间的接口依赖,例如这次高校云平台的项目,其他子系统都需要基础系统为其提供接口,因此极可能会造成这种情况,前期开发中基础系统一直在开发接口,而自己的功能只能放后!
怎么才能解决这个问题呢?隔离测试!它使得我们可以测试还未写完的代码 (只要你又接口可使用),另外,隔离测试能帮助团队单元测试代码的一部分,而无需等待全部代码的完成 。
单元测试一般有以下三种情况:普通测试、参数化测试和隔离测试。普通测试使用的是默认的运行器,而参数化测试用的是org.junit.runners.Parameterized,隔离测试会用到mockito。
(一)普通测试
待测方法:
//加法
public int add(int a, int b) {
return a + b;
}
测试方法:
@Test
publicvoidtestAdd(){
//--------------------第一种写法----------------------
//(1)待测方法的“参数赋值”
inta =1;
intb=3;
//(2)赋值后的“期望值”
intexpectedReturn=6;
//(3)调用待测方法,得到“实际值”
intactualReturn = firstDemo.add(1, 3);
//(4)通过断言,判断“期望值”和“实际值”是否相等
assertEquals(expectedReturn,actualReturn);
//---------------------第二种写法------------------------
//assertEquals(4,firstDemo.add(1,3));
}
测试方法的书写一般有四个步骤: ( 1 )参数赋值( 2 )写出期望值( 3 )获取实际值( 4 )断言 -- 比较期望值和实际值。
当参数情况较多时就需要进行参数化测试了。
(二)参数化测试
上面第一个普通测试,是针对一个方法只需要一个测试用例即可完成测试的情况。在实际项目中,我们会遇到一些分支语句,这时候一个测试用例已经不能满足我们 覆盖全部分支语句 了。所以我们就需要写多个测试用例,可是我们必须针对这个待测方法,写多个测试方法吗?这也太麻烦了吧!没有什么解决办法吗?如果是 Junit3 的话,我们只能这样做,可是从 Junit4 开始,加入了一个新的概念 -- 参数化测试。这就为我们解决了这个问题。
参数化测试主要包括五个步骤:
( 1 )为准备使用参数化测试的测试类指定特殊的运行器 org.junit.runners.Parameterized 。
@RunWith (Parameterized.class )
( 2 )为测试类声明几个变量,分别用于存放期望值和测试所用数据,期望值可能只有一个,但测试数据变量可能有好几个,比如加法中有两个变量才能得出一个结果。
( 3 )为测试类声明一个带有参数的公共构造函数 ,并在其中为第二个环节中声明的几个变量赋值,构造方法是Junit调用的 ☆关键点☆
( 4 )为测试类声明一个使用注解 org.junit.runners.Parameterized.Parameters 修饰的,返回值为 java.util.Collection 的公共静态 方法,并在此方法中初始化所有需要测试的参数对。 ☆关键点☆
( 5 )编写测试方法,使用定义的变量作为参数进行测试。
测试方法:
@RunWith(Parameterized.class)//第一步:指定特殊的运行器org.junit.runners.Parameterized
public class FirstDemoTestParameterization {
//要测试的类
private FirstDemo firstDemo;
//第二步:为测试类声明几个变量,分别用于存放期望值和测试所用数据。
private int input1;
private boolean expected;
@Before //执行每个测试方法之前都执行一次
public void setUp() throws Exception {
firstDemo = newFirstDemo();
}
//第三步:带有参数的公共构造函数,并在其中为声明的几个变量赋值。
public FirstDemoTestParameterization(int input1,boolean expected) {
this.input1 = input1; //参数1
this.expected = expected; //期待的结果值
}
//-------------------(1)参数赋值 &&&(2)写出期望值----------------------------
//第四步:为测试类声明一个注解@Parameters,返回值为Collection的公共静态方法,并初始化所有需要测试的参数对。
@Parameters
public static Collection prepareData() {
Object[][]object = { { -1,true }, { 13, true } }; //测试数据
returnArrays.asList(object); //将数组转换成集合返回
}
@Test
public void testParameterization() {
//-----------(3)获取实际值&&&(4)断言--比较期望值和实际值。---------------
//第五步:编写测试方法,使用定义的变量作为参数进行测试。
assertEquals(expected,firstDemo.Parameterization(input1));
}
}
但有时候测试会依赖其他单元,而单元测试要独立测试,因此需要把其他单元隔离,就有了隔离测试。
Parameterized是在参数上实现了Suit——修饰一个测试类,但是可以提供多组构造函数的参数用于测试不同场景。
示例2:
@RunWith(Parameterized.class)
public class TestGenerateParams
{
private String greeting;
public TestGenerateParams(String greeting)
{
super();
this.greeting = greeting;
}
@Test
public void testParams()
{
System.out.println(greeting);
}
/**
* 这里的返回至少是二维数组
* @return
*/
@Parameters
public static List getParams()
{
return
Arrays.asList(new String[][]{{"hello"},
{"hi"},
{"good morning"},
{"how are you"}});
}
}
输出结果:
hello
hi
good morning
how are you
getParams提供一组参数,根据JUnit的生命周期,在每次运行测试方法的时候都会调用Constructor来创建一个实例,这里构造函数的参数就是通过Collection传入的。因此如你所见我们需要一个含有参数的构造函数用于接收参数,这个参数需要用于跑测试用例所以把它保存做类的变量;然后用@Parameters修饰我们提供参数的静态方法,它需要返回List
,List包含的是参数组,Object[]即按顺序提供的一组参数,它会按照参数组一个一个的创建实例,相当于写了多个@Test。要想实现参数化测试,必须要有构造方法。
官方示例:
@RunWith(Parameterized.class)
public class FibonacciTest {
@Parameters
public static Collection data() {
return Arrays.asList(new Object[][] {
{ 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 }, { 5, 5 },{ 6, 8 }
});
}
private int fInput;
private int fExpected;
public FibonacciTest(int input, int expected) {
fInput= input;
fExpected= expected;
}
@Test
public void test() {
assertEquals(fExpected, Fibonacci.compute(fInput));
}
}
注:
Each instance of FibonacciTest will be constructed using the two-argument constructor and the data values in the @Parameters
method.
(三)隔离测试
隔离测试也是我们常用的一个 防止多类之间依赖 的测试。最基础的就是 B 层对 D 层的依赖。测试 B 层时,我们不可能还要跑 D 层,这样的话就不是单元测试。那么我们怎么来解决这个问题呢?我们不需要跑 D 层,但是又需要 D 层的返回值。隔离测试就帮助我们解决了这个问题。 当我们依赖其他类时,不需要真实调用,只需mock出该类即可。
================================================================================================
介绍了以上这些,我们来看下Junit4的具体用法和Mockito的用法。
(一)Junit4
1.Junit4.x版本我们常用的注解:
A、@Before 注解:与junit3.x中的setUp()方法功能一样,在每个测试方法之前执行;
B、@After 注解:与junit3.x中的tearDown()方法功能一样,在每个测试方法之后执行;
C、@BeforeClass 注解:在所有方法执行之前执行;
D、@AfterClass 注解:在所有方法执行之后执行;
E、@Test(timeout = xxx) 注解:设置当前测试方法在一定时间内运行完,否则返回错误;
F、@Test(expected = Exception.class) 注解:设置被测试的方法是否有异常抛出。抛出异常类型为:Exception.class;
G、@Ignore 注解:注释掉一个测试方法或一个类,被注释的方法或类,不会被执行。
2.Junit4.x版本我们常用的断言:assertEquals、assertTrue/False、assertNotNull/Null、assertSame/NotSame、assertThat(这个用的比较多,可以参看Junit4官网)、fail等,断言如果抛出异常,就会中断该测试方法,对其他测试方法无影响。
关于AssertThat和Hamcrest可以看:http://www.blogjava.net/DLevin/archive/2012/05/12/377960.html、http://blog.csdn.net/shrekmu/article/details/3018392
3.Junit的生命周期
public class HelloActionTest {
public HelloActionTest() {
System.out.println("Constructor");
}
@BeforeClass
public static void initClass() {
System.out.println("BeforeClass");
}
@Before
public void init() {
System.out.println("Before");
}
@Test
public void test1() {
System.out.println("test1");
}
@Test
public void test2() {
System.out.println("test2");
}
@After
public void after() {
System.out.println("after");
}
@AfterClass
public static void afterClass() {
System.out.println("afterClass");
}
}
运行结果:
BeforeClass
Constructor
Before
test1
after
Constructor
Before
test2
after
afterClass
@BeforeClass:修饰static的方法,在整个类执行之前执行该方法一次。比如你的测试用例执行前需要一些高开销的资源(连接数据库)可以用@BeforeClass搞定。值得注意的是如果测试用例类的父类中也存在@BeforeClass修饰的方法,它将在子类的@BeforeClass之前执行。和静态代码块类似,但低于它的优先级,即如果有静态代码块,就先执行静态代码块
@AfterClass:同样修饰static的方法,在整个类执行结束前执行一次。如果你用@BeforeClass创建了一些资源现在是时候释放它们了。
@Before:修饰public void的方法,在每个测试用例(方法)执行时都会执行。
@After:修饰public void的方法,在每个测试用例执行结束后执行。
Constructor:
每个测试用例(即每个@Test) 都会重新创建当前的Class实例,可以看到Constructor执行了两次。
具体的用法可参考http://junit.org/
(二)Mockito(用于模拟对象,隔离测试)
所谓的mock,就是指,如果我们写的代码依赖于某些对象,而这些对象又很难手动创建(即不知道如何初始化等,像HttpRequest等对象),那么就用一个虚拟的对象来测试。因为它传入的是一个class文件,所以static代码块还是会被运行,但构造函数,实例代码块都不会被执行
所谓打桩Stub,就是用来提供测试时所需要的测试数据,因为是mock的对象,所以可能有些方法并不能知道返回值,因此我们需要去假定返回值。可以对各种交互设置相应的回应,即对方法设置调用返回值,使用when(...).thenReturn(...)。
例如
List list = mock(List.class);
//因为mock出的list不知道有什么值,可以假定。
when(list.get(0)).thenReturn("you get it");
when(list.get(1)).thenReturn(new Exception);
验证方法的调用使用verify(...).xxxMethod(...)
整个框架就是围绕模拟 (无论是模拟对象还是方法还是方法的参数)和验证 (验证是否调用,调用顺序,调用次数等)
模拟
:
1.1 stubbing
//You can mock concrete classes, not only interfaces
LinkedList mockedList = mock(LinkedList.class);
//stubbing
when(mockedList.get(0)).thenReturn("first");
when(mockedList.get(1)).thenThrow(new RuntimeException());
//following prints "first"
System.out.println(mockedList.get(0));
//following throws runtime exception
System.out.println(mockedList.get(1));
//following prints "null" because get(999) was not stubbed
System.out.println(mockedList.get(999));
//Although it is possible to verify a stubbed invocation, usually it's just redundant
//If your code cares what get(0) returns then something else breaks (often before even verify() gets executed).
//If your code doesn't care what get(0) returns then it should not be stubbed.
verify(mockedList).get(0);
默认情况下,对于所有有返回值且没有stub过的方法,mockito会返回相应的默认值。
对于内置类型会返回默认值,如int会返回0,布尔值返回false。对于其他type会返回null。重复多次stubbing,以最后一次为准。
1.2连续的stubbing
when(mock.someMethod("some arg"))
.thenThrow(new RuntimeException())
.thenReturn("foo");
/*等价于when(mock.someMethod("some arg")).thenReturn("one","two","three")*/
//First call: throws runtime exception:
mock.someMethod("some arg");
//Second call: prints "foo"
System.out.println(mock.someMethod("some arg"));
//Any consecutive call: prints "foo" as well (last stubbing wins).
System.out.println(mock.someMethod("some arg"));
1.3对于无返回值的stubbing的情况
doThrow(new RuntimeException()).when(mockedList).clear();
//following throws RuntimeException:
mockedList.clear();
2. Argument matchers
//stubbing using built-in anyInt() argument matcher
when(mockedList.get(anyInt())).thenReturn("element");
//stubbing using custom matcher (let's say isValid() returns your own matcher implementation):
when(mockedList.contains(argThat(isValid()))).thenReturn("element");
//following prints "element"
System.out.println(mockedList.get(999));
//you can also verify using an argument matcher
verify(mockedList).get(anyInt());
注:如果你使用了参数匹配器,那么所有的参数都需要有匹配器给出
verify(mock).someMethod(anyInt(), anyString(), eq("third argument"));
//above is correct - eq() is also an argument matcher
verify(mock).someMethod(anyInt(), anyString(), "third argument");
//above is incorrect - exception will be thrown because third argument is given without an argument matcher.
验证 :
1.验证方法调用次数
//using mock
mockedList.add("once");
mockedList.add("twice");
mockedList.add("twice");
mockedList.add("three times");
mockedList.add("three times");
mockedList.add("three times");
//following two verifications work exactly the same - times(1) is used by default
verify(mockedList).add("once");
verify(mockedList, times(1)).add("once");
//exact number of invocations verification
verify(mockedList, times(2)).add("twice");
verify(mockedList, times(3)).add("three times");
//verification using never(). never() is an alias to times(0)
verify(mockedList, never()).add("never happened");
//verification using atLeast()/atMost()
verify(mockedList, atLeastOnce()).add("three times");
verify(mockedList, atLeast(2)).add("five times");
verify(mockedList, atMost(5)).add("three times");
2.验证调用顺序
// A. Single mock whose methods must be invoked in a particular order
List singleMock = mock(List.class);
//using a single mock
singleMock.add("was added first");
singleMock.add("was added second");
//create an inOrder verifier for a single mock 关键点
InOrder inOrder = inOrder(singleMock);
//following will make sure that add is first called with "was added first, then with "was added second"
inOrder.verify(singleMock).add("was added first");
inOrder.verify(singleMock).add("was added second");
// B. Multiple mocks that must be used in a particular order
List firstMock = mock(List.class);
List secondMock = mock(List.class);
//using mocks
firstMock.add("was called first");
secondMock.add("was called second");
//create inOrder object passing any mocks that need to be verified in order
InOrder inOrder = inOrder(firstMock, secondMock);
//following will make sure that firstMock was called before secondMock
inOrder.verify(firstMock).add("was called first");
inOrder.verify(secondMock).add("was called second");
// Oh, and A + B can be mixed together at will
注:验证顺序是要灵活变通的,一般你不需要去对每一个互动都去验证调用顺序,而只是在你感兴趣的互动上去验证调用顺序。
详细的可参考:http://site.mockito.org/mockito/docs/current/org/mockito/Mockito.html
总结:单元测试中一般要结合Junit和Mockito一起测试。
百度文库关于mockito的详细介绍:http://wenku.baidu.com/link?url=dQQtWgqgbWB1m0XIxbWPiCm8j8b09inMHQJ-USe5CN7o6EcIIT03IUBNCLUrqnXDwgaXnMlrukMVRFreDpDeKTHfD9iaD6JNmb1myVWoYhm