Sunday, December 18, 2016

Mockito简介

Mockito是基于JUnit,可以先学习一下JUnit。原则是依赖什么,模拟什么
Mockito可以验证
1. 值是否相等(assertEquals)
2. 方法调用几次(verify)
3. 方法调用参数是否一致(eq/ArgumentCaptor)
4. 方法调用是否按顺序

Stub

比如一个FTP程序,要测试如果断开连接是否会log错误信息Disconnected from FTP。这里断开连接是测试前提(或叫条件)是Stub,会否log错误信息是测试对象(或叫目标)。when里面只能是mock对象,不能是真实对象。when(getName(anyString(), "abc")这是不对,因为参数如果一个是matcher,其他就一定要matcher(eq, any...), i.e. eq("ewf", any())

FTPClient mockedFTP = mock(FTPClient.class);
或者
@Mock
FTPClient mockedFTP

//Stub “无法连接”
when(mockedFTP.isConnected())).thenReturn(false);
when可以任意值如when(foo.add(lt(5), eq(8)))第一参数任意小于5的值及第二参数=8时返回。。
但thenReturn一定要具体指不能用any(Test.class)

//开始测试,真实调用
test.isFTPConnected();

//检查结果
assert(true, Logger.endWith("Disconnected from FTP"));

这样的测试看似显而易见,由代码一看就知道是正确,但为了确保后续维护或者refactor仍能保持这个逻辑(需求),有必要写这样的测试。

Mockito不支持连续mock,如when(mockedFTP.getConn().isConnected).thenReturn(true); 此情况可以逐个mock来做接力
@Mock Connect conn;
when(conn.isConnected).thenReturn(true);

any用于假设,any(class)用于verify
when(mockClass.get(any())).thenReturn();
verify(dynamoDao).put(any(List.class)

API:
doReturn
doNothing
doThrow

Exception的测试
  @Test(expected=ArithmeticException.class) public void testException2(){ doThrow(ArithmeticException.class).when(customer).getCustomerId(); // Act underTest.buildAnimal("tiger", 4); }

InOrder的测试
声明mock的预期调用顺序,verify再验证
// Arrange
InOrder inOrder = inOrder(customer, myJsonHelper);

// Act
underTest.callInOrder();

// Assert
inOrder.verify(customer).setCustomerId(Mockito.anyInt());
inOrder.verify(myJsonHelper).toObject();

Verify

Mockito包括stub, verify, spy三种API。Verify是检查mock对象的某一个函数+参数这个组合(参数不同视为不同调用)的调用次数。使用场合:如果测试对象不返回结果或结果极为复杂就用此校验法。

//真实调用
mockedList.add("one");
//验证add("one")这个调用是否一次
verify(mockedList).add("one");

另一个例子:
request.timestamp=1483685164
device.timestamp=1483685164
//真实调用
updateTimestampActivity.update(request);
//验证add("one")这个调用是否一次
verify(dynamoDao).put(devices);
verify(dynamoDao, times(0)).put(any(Device.class));

Main.java:
update(request){
    request.timestamp=Date.now();
    Devices devices = convertToObject(request);
    dynamoDB.put(devices);
}
这个例子中verify对象时dynamoDao的put方法以及devices参数,由于devices在真实调用中被修改成当前时间与1483685164不一致,所以会报错:参数不一样。代码要更改成verify(dynamoDao).put(any(List.class));表示匹配任何List参数也就是不验证参数。
Matchers.any(Test.class)
Matchers.anyListOf(Test.class)

参数捕捉:

public void test(){
    //act
     underTest.exec();

    //assert
     verify(math).pow(baseCaptor.capture(), exponenetCaptor.capture());
     Integer base = baseCaptor.getValue();
     Float exponenet = exponenetCaptor.getValue();

     assertEqual(2, base);
   
}

@Captor
ArgumentCaptor<Integer> baseCaptor;

@Captor
ArgumentCaptor<Float> exponenetCaptor;

@Mock
Math math;

@InjectMocks
Test underTest;

捕捉参数还可以用eq来代替,如verify(math).pow(eq(2), eq(1.3));等价于verify(math).pow(2,1,3)当prmitive类型时候。如果参数为复合类型,eq会调用equals去做匹配,如eq(student),而不加eq时候会比较指针。

Spy

spy是修改部分测试对象的部分API以满足测试条件,此法慎用。
假设UnderTest有两个函数getProfile(), getName(),而getProfile()内部调用getName(), 此时getName若用when().thenReturn做mock会报错因为getName()也是UnderTest的一个函数。这是可以用spy来修改UnderTest的API来满足。@InjectMocks和@Spy可以连用。例如:

StudentTest:

public void test(){
    //因为student是spy,所以student.get(Name)可以假设
    doReturn("Gary").when(student).getName();
}

@InjectMocks
@Spy
Student student;


Mockito用于有dependency情况下测试,JUnit是无条件下测试,两者结合使用。

Annotation:

annotation显得代码更简洁
annotation mock 功能
@RunWith(MockitoJUnitRunner.class) MockitoAnnotations.initMocks(this); 初始化
@Mock Mockito.mock(ArrayList.class) 自动产生实例,用于假设或叫依赖。如果mock类成员一定要与@InjectMocks连用,否则时null不自动产生实例
@Captor ArgumentCaptor.forClass(String.class) 参数捕捉
@InjectMocks MyDictionary dic; 自动产生实例。需要测试的obj也叫UnderTest,而它的域成员可以mock,用来测试每个函数

class Student {
     String name;
     public void exam(Dialog dialog){}
}

StudentTest:

@Mock
String name;

@InjectMocks
Student student;

@Mock
Dialog dialog;

这里dialog是独立的,即使没有InjectMocks也不影响。但name依赖于student的存在。

另一个例子:

@Test
public void test_BuildAnimal(){
// Arrange
when(customer.getCustomerId()).thenReturn(0);

// Act
Animal animal = underTest.buildAnimal("tiger", 4);

// Assert
assertEquals("0:tiger", animal.drivedName());

}

@InjectMocks
VideoSpeechlet underTest;

@Mock
Customer customer;

Mockito有一定局限性,解决方案是PowerMock

mock contructor:
https://lkrnac.net/blog/2014/01/mock-constructor/

EqualsBuilder
排除某些fields如id
assertTrue(EqualsBuilder.reflectionEquals(student, student2, new String[]{"id"}))
assertTrue(EqualsBuilder.reflectionEquals(student, student2, "id"))

Ref:
理论:
http://blog.csdn.net/zhangxin09/article/details/42422643
API:
http://blog.csdn.net/onlyqi/article/details/6544989
实例:
http://blog.csdn.net/onlyqi/article/details/6546589
JUnit:
http://blog.csdn.net/zhangxin09/article/details/42418441



1 comment: