JUnit
JUnit 常用注解:
- @Test : 表示要执行此区域内的测试代码
- @BeforeClass : 在开始第一个测试前执行
- @AfterClass : 全部测试执行完毕后执行
- @Before : 每次测试前执行
- @After : 每次测试后执行
- @Ignore : 标注了@Ignore注解的测试脚本将不会被执行,也可用于 class 层
- @RunWith
@Test
@Test 有两个属性,分别是expected()和timeout()。
- expected():校验测试脚本抛出指定类型的异常
- timeout():测试脚本的运行时间是否满足要求
// 期待抛出IndexOutOfBoundsException.class
@Test(expected=IndexOutOfBoundsException.class)
public void sample_assert_exception(){
int[] sampleArr = new int[]{1, 2, 3};
int indexOutOfBoundNumber = sampleArr[3];
}
// 脚本运行时间小于1000毫秒
@Test(timeout = 1000)
public void testTimeoutFailed() throws InterruptedException {
Thread.sleep(2000);
}
@RunWith
基于JUnit的测试脚本都是依靠Runner去解析和执行,@Test等所有注解的执行顺序、生命周期也都是由Runner决定的,所以Runner是JUnit的核心。
JUnit 中默认的 Runner 是 org.junit.runners.BlockJUnit4ClassRunner,由于我们使用 Spring,所以选用 SpringJUnit4ClassRunner,能够更加方便的引用配置文件
Assert
常用方法:
- assertEquals
- assertSame
- assertNotSame
- assertNull
- assertNotNull
- assertTrue
- assertFalse
- assertArrayEquals
## Hamcrest
Hamcrest 即为 matchers 的异位构词,它提供了更加强大的 assert 功能,所有验证都是通过AssertThat(actual, matcher)
来做校验。
由于 Hamcrest 引入了 matcher 的概念,我们可以进行更多的组合,同时可读性更高。Hamcrest 如此好用,以至于 JUnit 已经将其吸纳进去。如果你现在用的 JUnit 是4.4之后的版本,那你已经有了 Hamcrest。
org.hamcrest.Matchers 中定义了大量的 static 方法,用于生成各种 Matcher。
- 字符串相关 API:
- startsWith
- endsWith
- containsString
- Iterable 相关 API:
- contains
- hasItem
- containsInAnyOrder
- hasSize
- Map 相关 API:
- hasKey
- hasValue
- hasEntry
- 条件相关 API:
- allOf
- anyOf
- not
- both
示例:
MatcherAssert.assertThat(number,Matchers.greaterThan(5));
MatcherAssert.assertThat(text, Matchers.startsWith("Hello"));
MatcherAssert.assertThat(array, Matchers.hasItem("World"));
同时为了提高可读性,可将方法进行 import.
assertThat(number, greaterThan(5));
assertThat(text, startsWith("Hello"));
assertThat(array, hasItem("World"));
Mock 测试
Mock 测试就是在测试过程中,对于某些不容易构造(如 HttpServletRequest 必须在Servlet 容器中才能构造出来)或者不容易获取比较复杂的对象(如 JDBC 中的ResultSet 对象),用一个虚拟的对象(Mock 对象)来创建以便测试的测试方法。
Mock 最大的功能是帮你把单元测试的耦合分解开,如果你的代码对另一个类或者接口有依赖,它能够帮你模拟这些依赖,并帮你验证所调用的依赖的行为。
目前,在 Java 阵营中主要的 Mock 测试工具有 Mockito,JMock,EasyMock 等。我们接下来注重介绍 Mockito。
Mockito
Mockito 与 JUnit 不同,并不是单元测试框架,它是用于生成 mock 对象的工具,一个典型的例子就是使用模拟对象来模拟数据库 DAO 层。
现在主流的 Mock Framework 是 Mockito。通常的做法是联合 JUnit + Mockito 来进行测试。
当使用 Spring boot 进行开发时,依赖org.springframework.boot:spring-boot-starter-test
即可,test starter 会自动依赖junit、mockito、hamcrest。
mock 对象
import static org.mockito.Mockito.*;
//mock creation
List mockedList = mock(List.class);
//using mock object
mockedList.add("one");
mockedList.clear();
//verification
verify(mockedList).add("one");
verify(mockedList).clear();
stubbing
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));
verify(mockedList).get(0);
参数匹配器(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));
verify
verify 可以使用参数匹配器:
//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.
verify 确切的调用次数/at least x / never。times(1) 是默认的,因此,使用的 times(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");
Stubbing void 方法
doThrow(new RuntimeException()).when(mockedList).clear();
//following throws RuntimeException:
mockedList.clear();
doNothing().when(mock).someMethod();
按序 verify
// 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
确保指定 mock 上没有发生interactions
//using mocks - only mockOne is interacted
mockOne.add("one");
//ordinary verification
verify(mockOne).add("one");
//verify that method was never called on a mock
verify(mockOne, never()).add("two");
//verify that other mocks were not interacted
verifyZeroInteractions(mockTwo, mockThree);
校验是否有未校验的调用
//using mocks
mockedList.add("one");
mockedList.add("two");
verify(mockedList).add("one");
//following verification will fail
verifyNoMoreInteractions(mockedList);
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"));
doReturn
- thenReturn 不能用來 stub void method
- spy 不能搭配 thenReturn
- stub same method 多次不能用 thenReturn
- doReturn 在編譯期間不做 return value 的型別檢查,thenReturn 會做 type check
- thenReturn 會先去執行 stub 的 method,然後改寫掉 return value,如果 method 裡有些變數沒有 mock ,極可能會 null pointer exception
在真实对象上 spy
spy的意思是你可以修改某个真实对象的某些方法的行为特征,而不改变他的基本行为特征。
List list = new LinkedList();
List spy = spy(list);
//optionally, you can stub out some methods:
when(spy.size()).thenReturn(100);
//using the spy calls *real* methods
spy.add("one");
spy.add("two");
//prints "one" - the first element of a list
System.out.println(spy.get(0));
//size() method was stubbed - 100 is printed
System.out.println(spy.size());
//optionally, you can verify
verify(spy).add("one");
verify(spy).add("two");
不过spy在使用的时候有很多地方需要注意,一不小心就会导致问题,所以不到万不得已还是不要用spy。
List list = new LinkedList();
List spy = spy(list);
//Impossible: real method is called so spy.get(0) throws IndexOutOfBoundsException (the list is yet empty)
when(spy.get(0)).thenReturn("foo");
//You have to use doReturn() for stubbing
doReturn("foo").when(spy).get(0);
@Mock vs @InjectMocks
使用注解有利于减少重复代码,增强可读性,易于排查错误。
Mockito支持的注解有:
- @Mock
- @Spy
- @Captor
- @InjectMocks
- 自动生成 Mock 类
在需要 Mock 的属性上标记@Mock注解,生成Mock类即可。使用@Mock注解来定义mock对象有如下的优点:
- 方便mock对象的创建
- 减少mock对象创建的重复代码
- 提高测试代码可读性
- 自动注入Mock类到被测试类
只要在被测试类上标记 @InjectMocks,Mockito就会自动将标记@Mock、@Spy等注解的属性值注入到被测试类中。
public class ArticleManagerTest {
@Mock
private ArticleCalculator calculator;
@Mock
private ArticleDatabase database;
@Mock
private UserProvider userProvider;
@InjectMocks
private ArticleService;
}
网友评论