Skip to content
springside edited this page Sep 4, 2012 · 3 revisions

#Unit Test 单元测试是针对某个类的测试,又分为将依赖类完全以Mock形式隔离的纯单元测试,以及使用真实的依赖类的非纯粹单元测试两类。

##1. Mock## Mockito 有着比EasyMock更优雅的API。

官方最完整的文档

与EasyMock比,它分开了设定返回值的stub语句与验证mock被调用过的verify函数,两者不再连在一起,不再需要那种记录,然后replay()的语句,而是需要分别独立编写。

使用示例见quickstart中的AccoutServiceTest. 当然,Mockito的基本原理还是用cglib生成子类.

###创建Mock对象 原始写法:

public class AccountManagerTest {
    private AccountManager accountManager ;
    private UserDao mockedUserDao;
    
    public void setUp(){
        mockedDao = Mockito.mock(UserDao.class);
        accountManager = new AccountManager();
        accountManager.setUserDao(mockedUserDao);
    }

更优雅的写法:

public class AccountManagerTest {
    @@InjectMocks 
    private AccountManager accountManager;
    @Mock
    private UserDao mockedUserDao;
    
    public void setUp(){
        MockitoAnnotations.initMocks(this);
    }

###stub函数

Mockito.when(mockedList.get(Mockito.anyInt())).thenReturn("element");

如果你懒,不写任何stub设定,所有mock调用都返回Null。没返回值函数的更加根本不用写stub。 参数的模糊匹配很多时候很有用, 注意只要一个参数用了模糊匹配,所有参数都要用匹配语法,如果还有某个参数想精确控制就用eq().

Mockito.verify(mock).someMethod(Mockito.anyInt(), Mockito.eq("second parameter"));

如果要设定没返回值函数抛异常,不再需要easyMock那种奇怪的expectLastCall了,直接写.

Mockito.doThrow(new RuntimeException()).when(mock).clear();

Stub的最高级境界之一,使用Answer接口,计算输入参数来决定返回值

when(mock.someMethod(anyString())).thenAnswer(new Answer() {
      Object answer(InvocationOnMock invocation) {
	        Object[] args = invocation.getArguments();
	        return "called with arguments: " + args;
	    }
	});
	 
	System.out.println(mock.someMethod("foo"));

Stub的最高级境界之二,spy 真实Object,只stub其中部分的方法,其他方法继续真实的跑。

List list = new LinkedList();
List spy = Mockito.spy(list);
	 
//optionally, you can stub out some methods:
when(spy.size()).thenReturn(100);
//using the spy calls *real* methods
spy.add("one");

###verfiy函数:

简单的校验

Mockito.verify(mockUserDao).delete(2L);

如果要确认mock的方法没有被执行过

Mockito.verify(mockUserDao, Mockito.never()).delete(1L);

verify的最高境界之一,使用capture()把输入参数记下来再慢慢判断。

ArgumentCaptor<Person> argument = ArgumentCaptor.forClass(Person.class);
verify(mock).doSomething(argument.capture());
assertEquals("John", argument.getValue().getName());

##2. PowerMock Mockito对构造函数, 静态方法和final方法是无解的,此时需要JMockit或PowerMock来救场。因为JMockit是另一套mock API体系,而PowerMock的功能比较纯粹,只负责解决static,final的问题,而API与Mockito/EasyMock是兼容的,所以选了它。

与Mockito结合的完整示例

在SpringSide里的使用示例见spring-test中的WebDriverFactoryTest,对构造函数进行了Mock.

有个问题是PowerMock一般用PowerMockRunner来激活, 那些Spring applicationContext aware(见下)的测试也有自己的Runner. 这个时 候,就要用PowerMock的JunitRule,见 http://code.google.com/p/powermock/wiki/PowerMockRule

##3. Spring ApplicationContext Testcase

Spring提供了自己的Listener和Runner, 并提供了两个基类

1. AbstractJUnit4SpringContextTests, 使用了这些Runner和Listener,子类只需要定义自己的applicationContext.xml文件路径,即可以用annotation将需要的Bean注入到用例里,或者使用它的applicationContext成员变量动态获取任意Bean。

@ContextConfiguration(locations = { "/applicationContext-test.xml" })
public class BeanValidatorsTest extends AbstractJUnit4SpringContextTests

它最有特色的一点是在整个测试期间,缓存SpringContext,以在用例里标注的context文件路径为key, 比如两个都使用"applicationContext-test.xml"的用例,会重用同一个context,减少了重复创建的时间。

但如果还有一个用例标注为"applicationContext-test.xml,applicationContext-cxf.xml", 则会创建出一个新的Context,两个Context同时并存,如果两个Context都需要占用端口,都使用嵌入式内存数据库,就会有悲剧发生了.....这个时候, Spring提供一个Class和方法级别标注@DirtiesContext,会在方法或类完成后,执行该Context的close,并把它从缓存中抹掉。 必须在每个有冲突的TestClass上不厌其烦的加上这个标签。

2. AbstractTransactionalJUnit4SpringContextTests, 在AbstractJUnit4SpringContextTests的基础上,多用了一个TransactionalTestExecutionListener, 增加了事务管理的能力,默认Rollback全部事务。另外提供了countRowsInTable, deleteFromTables和executeSqlScript三个有用函数。 如果Context里有多数据源时请看Transaction章节。

###SpringSide的扩展

1. SpringContextTestCase, 继承于AbstractJUnit4SpringContextTests,但名字短多了,还设定了@ActiveProfiles("test")

2. SpringTransactionalTestCase, 继承于AbstractTransactionalJUnit4SpringContextTests, 同样名字短多了,且设定了@ActiveProfiles("test"),还多保存了一个dataSource变量以作后用。

##4. 测试Private方法 首先可以review一下设计,问一下为什么要单独测试private函数。然后:

  1. 通过SpringSide的Reflections或者spring的ReflectionTestUtils暴力反射调用。
  2. 将方法开放成public,并加上Guava的@ReflectionTestUtils注释。

个人喜欢前者。

返回参考手册

Clone this wiki locally