Android单元测试学习总结
文章目录
- 一、本地单元测试
- 1. 创建测试类
- 2. Assert类中的常用断言方法
- 3. 运行测试类
- 4. 运行单个测试方法或多个测试类
- 二、Mockito测试框架的使用
- 1. Mock概念的理解
- 2. Mockito中几种Mock对象的方式
- 3. 验证行为
- verify(T mock)函数的使用
- 使用`when(T methodCall)`函数
- 使用`thenAnswer`为回调做测试桩
- 使用`doCallRealMethod()`函数来调用某个方法的真实实现方法
- 使用`doNothing()`函数是为了设置void函数什么也不做
- 使用`doAnswer()`函数测试void函数的回调
- 需要使用doReturn函数代替thenReturn的情况
- 使用`doThrow()`函数来测试void函数抛出异常
- 4. 验证方法的调用次数
- 5. 参数匹配器 (matchers)
- 6. 使用InOrder验证执行执行顺序
- 7. 使用Spy监控真实对象
- 8. 使用ArgumentCaptor进行参数捕获
- 9. 使用@InjectMocks自动注入依赖对象
- 三、PowerMockito框架使用
- 1. 普通Mock的方式
- 2. Mock方法内部new出来的对象
- 3. Mock普通对象的final方法
- 4. Mock普通类的静态方法
- 5. verify静态方法的调用次数
- 6. 使用真实返回值
- 7. Mock私有方法
- 8. Mock普通类的私有变量
- 9. 对静态void方法进行Mock
- 10. Mock系统的final静态类
- 四、Robolectric测试框架的使用
- 五、Espresso测试框架的使用
Android单元测试主要分为以下两种
- 本地单元测试(Junit Test), 本地单元测试是纯java代码的测试,只运行在本地电脑的JVM环境上,不依赖于Android框架的任何api, 因此执行速度快,效率较高,但是无法测试Android相关的代码。
- 仪器化测试(Android Test),是针对Android相关代码的测试,需要运行在真机设备或模拟器上,运行速度较慢,但是可以测试UI的交互以及对设备信息的访问,得到接近真实的测试结果。
在Android Studio中新建一个项目的时候,app
的gradle
中会默认添加单元测试的相关依赖库:
dependencies {implementation fileTree(dir: 'libs', include: ['*.jar'])testImplementation 'junit:junit:4.12'androidTestImplementation 'com.android.support.test:runner:1.0.2'androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
其中testImplementation
添加的依赖就是本地化测试库, androidTestImplementation
添加的依赖则是Android环境下的测试库,同时,在项目的工程目录下也会默认创建好测试的目录:
其中app/src/test/
下面存放的是Junit本地测试代码,app/src/androidTest/
下面存放的是Android测试代码。
一、本地单元测试
进行本地单元测试需要先了解一些基本的Junit注解:
注解名称 | 含义 |
---|---|
@Test |
定义所在方法为单元测试方法,方法必须是public void
|
@Before | 定义所在方法在每个测试用例执行之前执行一次, 用于准备测试环境(如: 初始化类,读输入流等),在一个测试类中,每个@Test方法的执行都会触发一次调用 |
@After | 定义所在方法在每个测试用例执行之后执行一次,用于清理测试环境数据,在一个测试类中,每个@Test方法的执行都会触发一次调用。 |
@BeforeClass |
定义所在方法在测试类里的所有用例运行之前运行一次,方法必须是public static void ,用于做一些耗时的初始化工作(如: 连接数据库)
|
@AfterClass |
定义所在方法在测试类里的所有用例运行之后运行一次,方法必须是public static void ,用于清理数据(如: 断开数据连接)
|
@Test (expected = Exception.class) | 如果该测试方法没有抛出Annotation中的Exception类型(子类也可以),则测试失败 |
@Test(timeout=100) | 如果该测试方法耗时超过100毫秒,则测试失败,用于性能测试 |
@Ignore 或者 @Ignore(“太耗时”) | 忽略当前测试方法,一般用于测试方法还没有准备好,或者太耗时之类的 |
@FixMethodOrder | 定义所在的测试类中的所有测试方法都按照固定的顺序执行,可以指定3个值,分别是DEFAULT、JVM、NAME_ASCENDING(字母顺序) |
@RunWith | 指定测试类的测试运行器 |
更多可以参考Junit官网:https://junit.org/junit4/
1. 创建测试类
接下来就可以创建测试类,除了可以手动创建测试类外,可以利用AS快捷键:将光标选中要创建测试类的类名上->按下ALT + ENTER->在弹出的弹窗中选择Create Test
这会弹出下面的弹窗,或者鼠标在类名上右键选择菜单Go to–>Test,也会弹出下面的弹窗
勾选需要进行测试的方法,会自动生成一个测试类:
如果勾选了@Before
或@After
的话也会自动给你生成对应的测试方法
接下来编写测试方法,首先在要测试的目标类中写几个业务方法:
public class SimpleClass {public boolean isTeenager(int age) {if (age < 15) {return true;}return false;}public int add(int a, int b) {return a + b;}public String getNameById(int id) {if (id == 1) {return "小明";} else if (id == 2){return "小红";}return "";}
}
然后,测试类:
@RunWith(JUnit4.class)
public class SimpleClassTest {private SimpleClass simpleClass;@Beforepublic void setUp() throws Exception {simpleClass = new SimpleClass();}@Afterpublic void tearDown() throws Exception {}@Testpublic void isTeenager() {Assert.assertFalse(simpleClass.isTeenager(20));Assert.assertTrue(simpleClass.isTeenager(14));}@Testpublic void add() {Assert.assertEquals(simpleClass.add(3, 2), 5);Assert.assertNotEquals(simpleClass.add(3, 2), 4);}@Testpublic void getNameById() {Assert.assertEquals(simpleClass.getNameById(1), "小明");Assert.assertEquals(simpleClass.getNameById(2), "小红");Assert.assertEquals(simpleClass.getNameById(10), "");}
}
其中setUp()
是自动生成的添加了@Before
注解,这会在每个测试方法执行前执行,因此在这里创建一个目标对象,也可以选择添加@BeforeClass
注解但这时setUp()
应该改为静态的方法。然后在每个测试方法中编写测试用例,这里使用org.junit.Assert
包中的断言方法,有很多assertXXX
方法,可以自己选择用来判断目标方法的结果是否满足预期。
2. Assert类中的常用断言方法
方法 | 含义 |
---|---|
assertNull(Object object) | 断言对象为空 |
assertNull(String message, Object object) | 断言对象为空,如果不为空抛出异常携带指定的message信息 |
assertNotNull(Object object) | 断言对象不为空 |
assertNotNull(Object object) | 断言对象不为空,如果为空抛出异常携带指定的message信息 |
assertSame(Object expected, Object actual) | 断言两个对象引用的是同一个对象 |
assertSame(String message, Object expected, Object actual) | 断言两个对象引用的是同一个对象,否则抛出异常携带指定的message信息 |
assertNotSame(Object expected, Object actual) | 断言两个对象引用的不是同一个对象 |
assertNotSame(String message, Object expected, Object actual) | 断言两个对象引用的不是同一个对象,否则抛出异常携带指定的message信息 |
assertTrue(boolean condition) | 断言结果为true |
assertTrue(String message, boolean condition) | 断言结果为true, 为false时抛出异常携带指定的message信息 |
assertFalse(boolean condition) | 断言结果为false |
assertFalse(String message, boolean condition) | 断言结果为false, 为true时抛出异常携带指定的message信息 |
assertEquals(long expected, long actual) | 断言两个long 类型 expected 和 actual 的值相等 |
assertEquals(String message, long expected, long actual) | 断言两个long 类型 expected 和 actual 的值相等,如不相等则抛异常携带指定message信息 |
assertEquals(Object expected, Object actual) | 断言两个对象相等 |
assertEquals(String message, Object expected, Object actual) | 断言两个对象相等,如果不相等则抛出异常携带指定的message信息 |
assertEquals(float expected, float actual, float delta) | 断言两个 float 类型 expect 和 actual 在 delta 偏差值下相等,delta是误差精度 |
assertEquals(String message, float expected, float actual, float delta) | 断言两个 float 类型 expect 和 actual 在 delta 偏差值下相等,如果不相等则抛出异常携带指定的message信息 |
assertEquals(double expected, double actual, double delta) | 断言两个 double 类型 expect 和 actual 在 delta 偏差值下相等 |
assertEquals(String message, double expected,double actual, double delta) | 断言两个 double 类型 expect 和 actual 在 delta 偏差值下相等,如果不相等则抛出异常携带指定的message信息 |
assertArrayEquals(T[] expected, T[] actual) | 断言两个相同类型的数组的元素一一对应相等 |
assertArrayEquals(String message, T[] expected, T[] actual) | 断言两个相同类型的数组的元素一一对应相等,如果不相等则抛出异常携带指定的message信息 |
fail() | 直接让测试失败 |
fail(String message) | 直接让测试失败并给出message错误信息 |
assertThat(T actual, Matcher<? super T> matcher) | 断言actual和matcher规则匹配 |
assertThat(String reason, T actual, Matcher<? super T> matcher) | 断言actual和matcher规则匹配,否则抛出异常携带指定的reason信息 |
其中assertEquals
的方法,都对应有一个assertNotEquals
方法,这里不列了,assertThat
是一个强大的方法:
Assert.assertThat(1, is(1));Assert.assertThat(0, is(not(1)));Assert.assertThat("hello", startsWith("h"));List<String> items = new ArrayList<>();items.add("aaa");items.add("bbb");Assert.assertThat(items, hasItem("aaa"));
需要静态导入org.hamcrest.Matchers
类里面的方法,更多匹配方法请参考这个类。
3. 运行测试类
选中测试类右键Run运行,控制面板中就会显示测试结果:
如果所有的测试用例都正常返回了预期的结果,则面板中左侧每个测试方法前面会带一个绿色的对勾,否则方法前面会变成红色感叹号并且控制面板会输出异常,现在来改一个业务方法试一下:
public boolean isTeenager(int age) {if (age < 15) {return false;}return false;}
这里将age < 15
改为输出false,假设这是我们在编码的时候由于疏忽粗心造成的,然后运行测试类:
控制面板会告诉那一行出错了:
也就是说这里没有返回预期的结果,说明我们编写的业务逻辑是有错误的,这时就需要改bug了。
4. 运行单个测试方法或多个测试类
上面是运行的整个测试类,如果要运行测试类的单个方法,则鼠标只选中某个要运行的测试方法,然后右键选择Run即可。如果要同时运行多个测试类,而如果多个测试类在同一个包下面,则选中多个测试类所在的包目录,然后右键选择Run运行。否则可以通过下面的方式指定,创建一个空的测试类,然后添加注解:
@RunWith(Suite.class)
@Suite.SuiteClasses({SimpleClassTest.class, SimpleClass2Test.class})
public class RunMultiTest {}
运行这个测试类就可以将指定的测试类的方法一起运行。
二、Mockito测试框架的使用
前面介绍的只能测试不涉及Android相关Api的java代码用例,如果涉及到Android相关Api的时候,就不方便了,这时如果不依赖第三方库的话可能需要使用仪器化测试跑到Android设备上去运行,于是有一些比较好的第三方的替代框架可以来模拟使用Android的代码测试,Mockito就是基于依赖注入实现的一个测试框架。
1. Mock概念的理解
什么是Mock, 这个单词的中文意思就是“模仿”或者“虚假”的意思,也就是要模仿一个对象,为啥要模仿?
在传统的JUnit单元测试中,没有消除在测试中对对象的依赖,如A对象依赖B对象方法,在测试A对象的时候,我们需要构造出B对象,这样子增加了测试的难度,或者使得我们对某些类的测试无法实现。这与单元测试的思路相违背。
还有一个主要的问题就是本地单元测试由于是运行本地JVM环境,无法依赖Android的api,只靠纯Junit的测试环境很难模拟出完整的Android环境,导致无法测试Android相关的代码,而Mock就能解决这个问题,通过Mock能够很轻易的实现对象的模拟。
添加依赖:
dependencies {implementation fileTree(dir: 'libs', include: ['*.jar'])testImplementation 'org.mockito:mockito-core:2.19.0'....
}
2. Mockito中几种Mock对象的方式
使用之前通过静态方式导入会使用更方便:
// 静态导入会使代码更简洁import static org.mockito.Mockito.*;
直接mock一个对象:
@Testpublic void testMock() {SimpleClass mockSimple = Mockito.mock(SimpleClass.class);assertNotNull(mockSimple);}
注解方式mock一个对象:
@MockSimpleClass simple;@Beforepublic void setUp() {MockitoAnnotations.initMocks(this);}@Testpublic void testMock() {assertNotNull(simple);}
运行器方式mock一个对象:
@RunWith(MockitoJUnitRunner.class)
public class ExampleUnitTest {@MockSimpleClass simple;@Testpublic void testMock() {assertNotNull(simple);}
}
MockitoRule方式mock一个对象:
public class ExampleUnitTest {@MockSimpleClass simple;@Rule //<--使用@Rulepublic MockitoRule mockitoRule = MockitoJUnit.rule();@Testpublic void testMock() {assertNotNull(simple);}
}
3. 验证行为
verify(T mock)函数的使用
verify(T mock)
的作用是验证发生的某些行为等同于verify(mock, times(1))
例如:
@Test
public void testMock() {//创建mock对象List mockedList = mock(List.class);//使用mock对象mockedList.add("one");mockedList.clear();//验证mockedList.add("one")是否被调用,如果被调用则当前测试方法通过,否则失败verify(mockedList).add("one");//验证 mockedList.clear()是否被调用,如果被调用则当前测试方法通过,否则失败verify(mockedList).clear();}
@Test
public void testMock() {mock.someMethod("some arg");//验证mock.someMethod("some arg")是否被调用,如果被调用则测试方法通过,否则失败verify(mock).someMethod("some arg");}
也就是说如果把调用的方法注释掉,则运行testMock()方法就会失败。
通过verify
关键字,一旦mock对象被创建了,mock对象会记住所有的交互。然后你就可能选择性的验证你感兴趣的交互。
通常需要配合一些测试方法来验证某些行为,这些方法称为"打桩方法"(Stub),打桩的意思是针对mock出来的对象进行一些模拟操作,如设置模拟的返回值或抛出异常等。
常见的打桩方法:
方法名 | 方法含义 |
---|---|
doReturn(Object toBeReturned) | 提前设置要返回的值 |
doThrow(Throwable… toBeThrown) | 提前设置要抛出的异常 |
doAnswer(Answer answer) | 提前对结果进行拦截 |
doCallRealMethod() | 调用某一个方法的真实实现 |
doNothing() | 设置void函数什么也不做 |
thenReturn(T value) | 设置要返回的值 |
thenThrow(Throwable… throwables) | 设置要抛出的异常 |
thenAnswer(Answer<?> answer) | 对结果进行拦截 |
例如:
@Testpublic void testMock() {// 你可以mock具体的类型,不仅只是接口List mockedList = mock(List.class);// 打测试桩when(mockedList.get(0)).thenReturn("first");doReturn("aaaa").when(mockedList).get(1);when(mockedList.get(1)).thenThrow(new RuntimeException());doThrow(new RuntimeException()).when(mockedList).clear();// 输出“first”System.out.println(mockedList.get(0));// 因为get(999) 没有打桩,因此输出null, 注意模拟环境下这个地方是不会报IndexOutOfBoundsException异常的System.out.println(mockedList.get(999));// get(1)时会抛出异常System.out.println(mockedList.get(1));// clear会抛出异常mockedList.clear();}
doXXX
和thenXXX
使用上差不多,一个是调用方法之前设置好返回值,一个是在调用方法之后设置返回值。默认情况下,Mock出的对象的所有非void函数都有返回值,对象类型的默认返回的是null,例如返回int、boolean、String
的函数,默认返回值分别是0、false
和null
。
使用when(T methodCall)
函数
打桩方法需要配合when(T methodCall)
函数,意思是使测试桩方法生效。当你想让这个mock能调用特定的方法返回特定的值,那么你就可以使用它。
例如:
when(mock.someMethod()).thenReturn(10);//你可以使用灵活的参数匹配,例如 when(mock.someMethod(anyString())).thenReturn(10);//设置抛出的异常when(mock.someMethod("some arg")).thenThrow(new RuntimeException());//你可以对不同作用的连续回调的方法打测试桩://最后面的测试桩(例如:返回一个对象:"foo")决定了接下来的回调方法以及它的行为。when(mock.someMethod("some arg")).thenReturn("foo")//第一次调用someMethod("some arg")会返回"foo".thenThrow(new RuntimeException());//第二次调用someMethod("some arg")会抛异常//可以用以下方式替代比较小版本的连贯测试桩:when(mock.someMethod("some arg")).thenReturn("one", "two");//和下面的方式效果是一样的when(mock.someMethod("some arg")).thenReturn("one").thenReturn("two");//比较小版本的连贯测试桩并且抛出异常:when(mock.someMethod("some arg")).thenThrow(new RuntimeException(), new NullPointerException();
使用thenAnswer
为回调做测试桩
when(mock.someMethod(anyString())).thenAnswer(new Answer() {Object answer(InvocationOnMock invocation) {Object[] args = invocation.getArguments();Object mock = invocation.getMock();return "called with arguments: " + args;}});// 输出 : "called with arguments: foo"System.out.println(mock.someMethod("foo"));
使用doCallRealMethod()
函数来调用某个方法的真实实现方法
注意,在Mock环境下,所有的对象都是模拟出来的,而方法的结果也是需要模拟出来的,如果你没有为mock出的对象设置模拟结果,则会返回默认值,例如:
public class Person {public String getName() {return "小明";}
}@Test
public void testPerson() {Person mock = mock(Person.class);//输出null,除非设置发回模拟值when(mock.getName()).thenReturn("xxx");System.out.println(mock.getName());
}
因为getName()方法没有设置模拟返回值,而getName()返回值是String类型的,因此直接调用的话会返回String的默认值null,所以上面代码如果要想输出getName()方法的真实返回值的话,需要设置doCallRealMethod():
@Testpublic void testPerson() {Person mock = mock(Person.class);doCallRealMethod().when(mock).getName();//输出“小明”System.out.println(mock.getName());}
使用doNothing()
函数是为了设置void函数什么也不做
需要注意的是默认情况下返回值为void的函数在mocks中是什么也不做的但是,也会有一些特殊情况。如:
测试桩连续调用一个void函数时:
doNothing().doThrow(new RuntimeException()).when(mock).someVoidMethod();//does nothing the first time:mock.someVoidMethod();//throws RuntimeException the next time:mock.someVoidMethod();
监控真实的对象并且你想让void函数什么也不做:
List list = new LinkedList();
List spy = spy(list);//let's make clear() do nothing
doNothing().when(spy).clear();spy.add("one");//clear() does nothing, so the list still contains "one"
spy.clear();
使用doAnswer()
函数测试void函数的回调
当你想要测试一个无返回值的函数时,可以使用一个含有泛型类Answer参数的doAnswer()函数做回调测试。假设你有一个void方法有多个回调参数,当你想指定执行某个回调时,使用thenAnswer很难实现了,如果使用doAnswer()将非常简单,示例代码如下:
MyCallback callback = mock(MyCallback.class);
Mockito.doAnswer(new Answer() {@Overridepublic Object answer(InvocationOnMock invocationOnMock) throws Throwable {//获取第一个参数MyCallback call = invocation.getArgument(0);//指定回调执行操作call.onSuccess();return null;}}).when(mockedObject.requset(callback));doAnswer(new Answer() {@Overridepublic Object answer(InvocationOnMock invocation) throws Throwable {System.out.println("onSuccess answer");return null;}}).when(callback).onSuccess();mockedObject.requset(callback)
需要使用doReturn函数代替thenReturn的情况
如当监控真实的对象并且调用真实的函数带来的影响时
List list = new LinkedList();
List spy = spy(list);//不可能完成的:真实方法被调用的时候list仍是空的,所以spy.get(0)会抛出IndexOutOfBoundsException()异常
when(spy.get(0)).thenReturn("foo");//这时你应该使用doReturn()函数
doReturn("foo").when(spy).get(0);
使用doThrow()
函数来测试void函数抛出异常
SimpleClass mock = mock(SimpleClass.class);
doThrow(new RuntimeException()).when(mock).someVoidMethod();
mock.someVoidMethod();
总之使用doThrow(), doAnswer(), doNothing(), doReturn() and doCallRealMethod()
这些函数时可以在适当的情况下调用when()
来解决一些问题., 如当你需要下面这些功能时这是必须的:
- 测试void函数
- 在受监控的对象上测试函数
- 不只一次的测试同一个函数,在测试过程中改变mock对象的行为
4. 验证方法的调用次数
需要配合使用一些方法
方法 | 含义 |
---|---|
times(int wantedNumberOfInvocations) | 验证调用方法的次数 |
never() | 验证交互没有发生,相当于times(0) |
only() | 验证方法只被调用一次,相当于times(1) |
atLeast(int minNumberOfInvocations) | 至少进行n次验证 |
atMost(int maxNumberOfInvocations) | 至多进行n次验证 |
after(long millis) | 在给定的时间后进行验证 |
timeout(long millis) | 验证方法执行是否超时 |
description(String description) | 验证失败时输出的内容 |
verifyZeroInteractions | 验证mock对象没有交互 |
例如:
mock.someMethod("some arg");
mock.someMethod("some arg");
//验证mock.someMethod("some arg")被连续调用两次,即如果没有调用两次则验证失败
verify(mock, times(2)).someMethod("some arg");
//注意,下面三种是等价的,都是验证someMethod()被只调用一次
verify(mock).someMethod("some arg");
verify(mock, times(1)).someMethod("some arg");
verify(mock, only()).someMethod("some arg");
mPerson.getAge();
mPerson.getAge();
//验证至少调用2次
verify(mPerson, atLeast(2)).getAge();
//验证至多调用2次
verify(mPerson, atMost(2)).getAge();
//下面两种等价,验证调用次数为0
verify(mPerson, never()).getAge();
verify(mPerson, times(0)).getAge();
mPerson.getAge();
mPerson.getAge();
long current = System.currentTimeMillis();
System.out.println(current );
//延时1s后验证mPerson.getAge()是否被执行了2次
verify(mPerson, after(1000).times(2)).getAge();
System.out.println(System.currentTimeMillis() - current);
mPerson.getAge();mPerson.getAge();//验证方法在100ms超时前被调用2次verify(mPerson, timeout(100).times(2)).getAge();
@Testpublic void testVerifyZeroInteractions() {Person person = mock(Person.class);person.eat("a");//由于person对象发生了交互,所以这里验证失败,把上面的调用注释掉这里就会验证成功verifyZeroInteractions(person);//可以验证多个对象没有交互//verifyZeroInteractions(person,person2 );}
@Testpublic void testVerifyZeroInteractions() {Person person = mock(Person.class);person.eat("a");verify(person).eat("a");//注意,这将会无法到达验证目的,不能跟verify()混用verifyZeroInteractions(person,person2 );}
5. 参数匹配器 (matchers)
Mockito以自然的java风格来验证参数值: 使用equals()函数。有时,当需要额外的灵活性时你可能需要使用参数匹配器,也就是argument matchers :
// 使用内置的anyInt()参数匹配器when(mockedList.get(anyInt())).thenReturn("element");// 使用自定义的参数匹配器( 在isValid()函数中返回你自己的匹配器实现 )when(mockedList.contains(argThat(isValid()))).thenReturn("element");// 输出elementSystem.out.println(mockedList.get(999));// 你也可以验证参数匹配器verify(mockedList).get(anyInt());
常用的参数匹配器:
方法名 | 含义 |
---|---|
anyObject() | 匹配任何对象 |
any(Class type) | 与anyObject()一样 |
any() | 与anyObject()一样 |
anyBoolean() | 匹配任何boolean和非空Boolean |
anyByte() | 匹配任何byte和非空Byte |
anyCollection() | 匹配任何非空Collection |
anyDouble() | 匹配任何double和非空Double |
anyFloat() | 匹配任何float和非空Float |
anyInt() | 匹配任何int和非空Integer |
anyList() | 匹配任何非空List |
anyLong() | 匹配任何long和非空Long |
anyMap() | 匹配任何非空Map |
anyString() | 匹配任何非空String |
contains(String substring) | 参数包含给定的substring字符串 |
argThat(ArgumentMatcher matcher) | 创建自定义的参数匹配模式 |
eq(T value) | 匹配参数等于某个值 |
一些示例代码:
@Testpublic void testPersonAny(){when(mPerson.eat(any(String.class))).thenReturn("米饭");//或:when(mPerson.eat(anyString())).thenReturn("米饭");//输出米饭System.out.println(mPerson.eat("面条"));}@Testpublic void testPersonContains(){when(mPerson.eat(contains("面"))).thenReturn("面条");//输出面条System.out.println(mPerson.eat("面"));}@Testpublic void testPersonArgThat(){//自定义输入字符长度为偶数时,输出面条。when(mPerson.eat(argThat(new ArgumentMatcher<String>() {@Overridepublic boolean matches(String argument) {return argument.length() % 2 == 0;}}))).thenReturn("面条");//输出面条System.out.println(mPerson.eat("1234"));}
需要注意的是,如果你打算使用参数匹配器,那么所有参数都必须由匹配器提供。例如:
verify(mock).someMethod(anyInt(), anyString(), eq("third argument"));
// 上述代码是正确的,因为eq()也是一个参数匹配器verify(mock).someMethod(anyInt(), anyString(), "third argument");
// 上述代码是错误的, 因为所有参数必须由匹配器提供,而参数"third argument"并非由参数匹配器提供,因此会抛出异常
像anyObject(), eq()这样的匹配器函数不会返回匹配器。它们会在内部将匹配器记录到一个栈当中,并且返回一个假的值,通常为null。
6. 使用InOrder验证执行执行顺序
验证执行执行顺序主要使用InOrder
函数
如,验证mock一个对象的函数执行顺序:
@Testpublic void testInorder() {List<String> singleMock = mock(List.class);singleMock.add("小明");singleMock.add("小红");// 为该mock对象创建一个inOrder对象InOrder inOrder = inOrder(singleMock);// 验证add函数首先执行的是add("小明"),然后才是add("小红"),否则测试失败inOrder.verify(singleMock).add("小明");inOrder.verify(singleMock).add("小红");}
验证多个mock对象的函数执行顺序:
@Testpublic void testInorderMulti() {List<String> firstMock = mock(List.class);List<String> secondMock = mock(List.class);firstMock.add("小明");secondMock.add("小红");// 为这两个Mock对象创建inOrder对象InOrder inOrder = inOrder(firstMock, secondMock);// 验证它们的执行顺序inOrder.verify(firstMock).add("小明");inOrder.verify(secondMock).add("小红");}
验证执行顺序是非常灵活的,你不需要一个一个的验证所有交互,只需要验证你感兴趣的对象即可。 你可以选择单个mock对象和多个mock对象混合着来,也可以仅通过那些需要验证顺序的mock对象来创建InOrder对象。
7. 使用Spy监控真实对象
监控真实对象使用spy()
函数生成,或者也可以像@Mock那样使用@Spy
注解来生成一个监控对象, 当你你为真实对象创建一个监控(spy)对象后,在你使用这个spy对象时真实的对象也会也调用,除非它的函数被stub了。尽量少使用spy对象,使用时也需要小心形式。
@Testpublic void testSpy() {List<String> list = new ArrayList<>();List<String> spy = spy(list);// 你可以选择为某些函数打桩when(spy.size()).thenReturn(100);// 调用真实对象的函数spy.add("one");spy.add("two");// 输出第一个元素"one"System.out.println(spy.get(0));// 因为size()函数被打桩了,因此这里返回的是100System.out.println(spy.size());// 验证交互verify(spy).add("one");verify(spy).add("two");}
使用@Spy
生成监控对象:
@SpyPerson mSpyPerson;@Testpublic void testSpyPerson() {//将会输出Person 类中getName()的真实实现,而不是nullSystem.out.println(mSpyPerson.getName());}
理解监控真实对象非常重要!有时,在监控对象上使用when(Object)
来进行打桩是不可能或者不切实际的。因此,当使用监控对象时请考虑doReturn|Answer|Throw()
函数族来进行打桩。例如:
List list = new LinkedList();
List spy = spy(list);// 不可能实现 : 因为当调用spy.get(0)时会调用真实对象的get(0)函数,
// 此时会发生IndexOutOfBoundsException异常,因为真实List对象是空的when(spy.get(0)).thenReturn("foo");// 你需要使用doReturn()来打桩
doReturn("foo").when(spy).get(0);
8. 使用ArgumentCaptor进行参数捕获
参数捕获主要为了下一步的断言做准备,示例代码:
@Testpublic void argumentCaptorTest() {List<Object> mock = mock(List.class);mock.add("John");//构建要捕获的参数类型,这里是StringArgumentCaptor argument = ArgumentCaptor.forClass(String.class);//在verify方法的参数中调用argument.capture()方法来捕获输入的参数verify(mock).add(argument.capture());//验证“John”参数捕获assertEquals("John", argument.getValue());}
@Testpublic void argumentCaptorTest2() {List<Object> mock = mock(List.class);mock.add("Brian");mock.add("Jim");ArgumentCaptor argument = ArgumentCaptor.forClass(String.class);verify(mock, times(2)).add(argument.capture());//如果又多次参数调用,argument.getValue()捕获到的是最后一次调用的参数assertEquals("Jim", argument.getValue());//如果要获取所有的参数值可以调用argument.getAllValues()assertArrayEquals(new Object[]{"Brian","Jim"}, argument.getAllValues().toArray());}
9. 使用@InjectMocks自动注入依赖对象
有时我们要测试的对象内部需要依赖另一个对象,例如:
public class User {private Address address;public void setAddress(Address address) {this.address = address;}public String getAddress() {return address.getDetail();}
}
public class Address {public String getDetail() {return "detail Address";}
}
User类内部需要依赖Address类,当我们测试的时候需要mock出这两个对象,然后将Address对象传入到User当中,这样如果依赖的对象多了的话就相当麻烦,Mockito 提供了可以不用去手动注入对象的方法,首先使用@InjectMocks
注解需要被注入的对象,如User,然后需要被依赖注入的对象使用@Mock
或@Spy
注解,之后Mockito 会自动完成注入过程,例如:
@InjectMocksUser mTestUser;@MockAddress mAddress;@Testpublic void argumentInjectMock() {when(mAddress.getDetail()).thenReturn("浙江杭州");System.out.println(mTestUser.getAddress());}
这样就不用关心为User 设置Address ,只要为User需要依赖的类添加注解就可以了,然后直接将重点放到测试方法的编写上。
或者使用@Spy监控真实对象注入也可以:
@InjectMocksUser mTestUser;@SpyAddress mAddress;@Testpublic void argumentInjectMock() {// when(mAddress.getDetail()).thenReturn("浙江杭州");System.out.println(mTestUser.getAddress());}
其他:
连续调用的另一种更简短的版本:
// 第一次调用时返回"one",第二次返回"two",第三次返回"three"when(mock.someMethod("some arg")).thenReturn("one", "two", "three");
参考:Mockito 中文文档
三、PowerMockito框架使用
Mockito框架基本满足需求但是有一些局限性,如对static、final、private等方法不能mock,PowerMockito就可以解决这些问题,PowerMockito是一个扩展了其它如EasyMock等mock框架的、功能更加强大的框架。PowerMock使用一个自定义类加载器和字节码操作来模拟静态方法,构造函数,final类和方法,私有方法,去除静态初始化器等等。
添加依赖:
testImplementation 'org.powermock:powermock-module-junit4:2.0.2'testImplementation 'org.powermock:powermock-module-junit4-rule:2.0.2'testImplementation 'org.powermock:powermock-api-mockito2:2.0.2'testImplementation 'org.powermock:powermock-classloading-xstream:2.0.2'
1. 普通Mock的方式
目标类:
public class CommonExample {public boolean callArgumentInstance(File file) {return file.exists();}
}
测试类:
public class CommonExamplePowerMockTest {@Testpublic void testCallArgumentInstance() {File file = PowerMockito.mock(File.class);CommonExample commonExample = new CommonExample();PowerMockito.when(file.exists()).thenReturn(true);Assert.assertTrue(commonExample.callArgumentInstance(file));}}
普通Mock方式是外部传递Mock参数,基本上和单独使用Mockito是一样的,使用纯Mockito的api也可以完成这个测试。
2. Mock方法内部new出来的对象
public class CommonExample {public boolean callArgumentInstance(String path) {File file = new File(path);return file.exists();}
}
@RunWith(PowerMockRunner.class)
@PrepareForTest(CommonExample.class)
public class CommonExamplePowerMockTest {@Testpublic void callCallArgumentInstance2() throws Exception {File file = PowerMockito.mock(File.class);CommonExample commonExample = new CommonExample();PowerMockito.whenNew(File.class).withArguments("aaa").thenReturn(file);PowerMockito.when(file.exists()).thenReturn(true);Assert.assertTrue(commonExample.callArgumentInstance("aaa"));}
}
跟前面有一点区别的就是,这里要测试的方法内部创建了File对象,这时需要通过PowerMockito.whenNew(File.class).withArguments("aaa").thenReturn(file)
方法模拟创建File的操作,当File类以aaa的参数创建的时候返回已经mock出来的file对象。同时这时需要在测试类上添加注解@RunWith(PowerMockRunner.class)
和@PrepareForTest(CommonExample.class)
,注意是在类上面添加,不是在方法上,一开始在方法上添加时提示找不到测试方法,@PrepareForTest()
括号里面指定的是要测试的目标类。
3. Mock普通对象的final方法
public class CommonExample {public boolean callFinalMethod(DependencyClass dependency) {return dependency.isValidate();}
}public class DependencyClass {public final boolean isValidate() {// do somethingreturn false;}
}
@RunWith(PowerMockRunner.class)
@PrepareForTest({CommonExample.class, DependencyClass.class})
public class CommonExamplePowerMockTest {@Testpublic void callFinalMethod() {DependencyClass dependency = PowerMockito.mock(DependencyClass.class);CommonExample commonExample = new CommonExample();PowerMockito.when(dependency.isValidate()).thenReturn(true);Assert.assertTrue(commonExample.callFinalMethod(dependency));}
}
同样这里mock出来需要依赖的类的对象,然后传递给调用方法,这里同样需要添加@RunWith
和@PrepareForTest
,@PrepareForTest
可以指定多个目标类,但是这里如果你只需要测试final的话,只添加DependencyClass.class一个就可以了。
4. Mock普通类的静态方法
public final class Utils {public static String getUUId() {return UUID.randomUUID().toString();}
}public class CommonExample {public String printUUID() {return Utils.getUUId();}
}
@RunWith(PowerMockRunner.class)
@PrepareForTest(Utils.class)
public class StaticUnitTest {@Beforepublic void setUp() throws Exception {PowerMockito.mockStatic(Utils.class);}@Testpublic void getUUId() {PowerMockito.when(Utils.getUUId()).thenReturn("FAKE UUID");CommonExample commonExample = new CommonExample();assertThat(commonExample.printUUID(), is("FAKE UUID"));}
}
同样需要指定@RunWith
和@PrepareForTest
,@PrepareForTest
中指定静态方法所在的类,测试静态方法之前需要调用PowerMockito.mockStatic()
方法来mock静态类,然后就通过when().thenReturn()
方法指定静态方法的模拟返回值即可。
5. verify静态方法的调用次数
@Testpublic void testVerify() {PowerMockito.when(Utils.getUUId()).thenReturn("FAKE UUID");CommonExample commonExample = new CommonExample();System.out.println(commonExample.printUUID());PowerMockito.verifyStatic(Utils.class);Utils.getUUId();}
静态方法通过PowerMockito.verifyStatic(Class c)
进行验证,不过这里跟Mocktio有一点区别的是需要在这个方法的后面再调用一次静态方法,否则不行。这里PowerMockito.verifyStatic(Utils.class)
其实等同于PowerMockito.verifyStatic(Utils.class, times(1))
,如果想验证超过一次的,那么:
@Testpublic void testVerify() {PowerMockito.when(Utils.getUUId()).thenReturn("FAKE UUID");CommonExample commonExample = new CommonExample();System.out.println(commonExample.printUUID());System.out.println(commonExample.printUUID());PowerMockito.verifyStatic(Utils.class, Mockito.times(2));Utils.getUUId();}
这时PowerMockito.verifyStatic()第一个参数指定静态方法类的Class,第二个参数接收一个VerificationMode类型的参数,因此传递Mockito中的任何验证方法次数的函数都可以,Mockito中的验证函数会返回的是一个VerificationMode类型。同样在PowerMockito.verifyStatic
方法后面要调用一次要验证的静态方法,总感觉这里很奇怪。。。
6. 使用真实返回值
如果在测试的过程中又遇到不需要mock出来的静态方法的模拟返回值,而是需要真实的返回值,怎么办呢,其实跟Mockito一样,PowerMockito同样提供thenCallRealMethod
或者doCallRealMethod
方法:
@Testpublic void testRealCall() throws Exception {PowerMockito.when(Utils.getUUId()).thenReturn("FAKE UUID");//...PowerMockito.when(Utils.getUUId()).thenCallRealMethod();//与下面等价//PowerMockito.doCallRealMethod().when(Utils.class, "getUUId");System.out.println(Utils.getUUId());}
或者直接使用spy监控真实对象也可以:
@Testpublic void testRealCall() {PowerMockito.spy(Utils.class);System.out.println(Utils.getUUId());}
7. Mock私有方法
public class CommonExample {public boolean callPrivateMethod() {return isExist();}private boolean isExist() {return false;}}
@RunWith(PowerMockRunner.class)
@PrepareForTest(CommonExample.class)
public class PrivateUnitTest {@Testpublic void testCallPrivateMethod() throws Exception {CommonExample commonExample = PowerMockito.mock(CommonExample.class);PowerMockito.when(commonExample.callPrivateMethod()).thenCallRealMethod();PowerMockito.when(commonExample, "isExist").thenReturn(true);Assert.assertTrue(commonExample.callPrivateMethod());}
}
在使用上跟纯Mockito的没有太大区别,只不过Mock私有方法是通过下面的api实现的:
PowerMockito.when(Object instance, String methodName, Object... arguments)
在PowerMockito中when函数与Mockito相比,最大的变化就是多了一些传递String类型的methodName的重载方法,这样在使用上几乎无所不能了。
8. Mock普通类的私有变量
public class CommonExample {private static final int STATE_NOT_READY = 0;private static final int STATE_READY = 1;private int mState = STATE_NOT_READY;public boolean doSomethingIfStateReady() {if (mState == STATE_READY) {// DO some thingreturn true;} else {return false;}}}
@Testpublic void testDoSomethingIfStateReady() throws Exception {CommonExample sample = new CommonExample();Whitebox.setInternalState(sample, "mState", 1);assertThat(sample.doSomethingIfStateReady(), is(true));}
通过Whitebox.setInternalState
来改变私有成员变量,这种情况下不需要指定@RunWith
和@PrepareForTest
。
9. 对静态void方法进行Mock
public class CommonExample {public static void doSomething(String a) {System.out.println("doSomething"+a);}
}
@RunWith(PowerMockRunner.class)
@PrepareForTest({CommonExample.class})
public class StaticUnitTest {@Testpublic void testStaticVoid() throws Exception {PowerMockito.mockStatic(CommonExample.class);PowerMockito.doNothing().when(CommonExample.class, "doSomething", Mockito.any());CommonExample.doSomething("aaa");}
}
默认情况下通过PowerMockito.mockStatic
的静态类的void的方法是什么也不做的,但是可以显示的执行doNothing, 上面的代码将doNothing那行注释掉也是什么也不做的。那如果想做一些事而不是doNothing呢,跟Mockito一样,采用doAnswer
:
@Testpublic void testStaticVoid() throws Exception {PowerMockito.mockStatic(CommonExample.class);PowerMockito.doAnswer(new Answer<Object>() {@Overridepublic Object answer(InvocationOnMock invocation) throws Throwable {System.out.println(invocation.getArguments()[0]);return null;}}).when(CommonExample.class, "doSomething", Mockito.any());CommonExample.doSomething("aaa");}
10. Mock系统的final静态类
public class CommonExample {public int callSystemStaticMethod(int a, int b) {return Math.max(a, a+b);}
}
@RunWith(PowerMockRunner.class)
@PrepareForTest(CommonExample.class)
public class StaticUnitTest {@Testpublic void callSystemStaticMethod() {CommonExample commonExample = new CommonExample();PowerMockito.mockStatic(Math.class);PowerMockito.when(Math.max(anyInt(), anyInt())).thenReturn(100);Assert.assertEquals(100, commonExample.callSystemStaticMethod(10, -5));}
}
@PrepareForTest
中添加调用系统类所在的类,这里需要注意的是如果你使用PowerMockito来mock系统静态final类,则gradle依赖中不能再添加单纯Mockito的依赖库,否则这里将不能mock成功,会提示Mockito can not mock/spy final class
, 因为PowerMockito本身已经有对Mockito的依赖库支持了,所以只依赖PowerMockito就可以了。除了系统静态final类的情况,其他的情况下PowerMockito和Mockito可以同时依赖(我测试是没有问题的)。另外单纯的Mockito新版本中也支持对 final 类 final 方法的 Mock,但是需要添加配置文件并不友好。
四、Robolectric测试框架的使用
由于Robolectric部分的内容比较长,所以单独放了一篇文章中:Android单元测试框架Robolectric的学习使用
五、Espresso测试框架的使用
Espresso是用于Android仪器化测试的测试框架,是谷歌官方主推的一个测试库。由于Espresso部分的内容也比较长,所以单独放了一篇文章中:Espresso测试框架的使用
Android单元测试学习总结相关推荐
- Android 单元测试学习计划
网上查了一下Android单元测试相关的知识点,总结了一个学习步骤: 1. 什么是单元测试 2. 单元测试正反面: 2.1. 重要性 2.2. 缺陷 2.3. 策略 3. 单元测试的基础知识: 3.1 ...
- Android单元测试学习之 Junit4
保持单元测试的独立性,单元测试用例之间绝不能互相调用,也不能依赖执行的先后次序 Repeatable 重复性 单元测试是可以重复执行的,不能收到外界环境的影响,因为单元测试通常会被放到持续集成中,每次 ...
- Android单元测试全解
自动化测试麻烦吗?说实在,麻烦!有一定的学习成本.但是,自动化测试有以下优点: 节省时间:可以指定测试某一个activity,不需要一个个自己点 单元测试:既然Java可以进行单元测试,Andro ...
- Android单元测试框架Robolectric3.0介绍(二)
文章中的所有代码在此:https://github.com/geniusmart/LoveUT ,由于 Robolectric 3.0 和 3.1 版本(包括后续3.x版本)差异不小,该工程中包含这两 ...
- Android单元测试框架Robolectric3.0介绍(一)
Android单元测试框架Robolectric3.0介绍(一) 作者 geniusmart 关注 2016.01.21 00:37* 字数 1550 阅读 18265评论 55喜欢 124赞赏 2 ...
- Android 单元测试,从小白到入门开始
目录 1 引言 1.1 背景 1.2 术语和缩略语 2 闲谈单测 2.1 说说我理解的单测 2.1.1 对测试金字塔的理解 2.1.2 为什么要做单测? 2.1.3 需要写 UI 测试吗? 2.1. ...
- Android单元测试(一):JUnit框架的使用
JUnit框架的使用 前言 工程介绍 JUnit介绍 什么是JUnit JUnit引入 JUnit注解 JUnit断言 JUnit使用 基础用法 参数化测试 assertThat用法 用法 自定义匹配 ...
- android测试入门选择哪个工具何时,Android单元测试——辅助工具介绍
8种机械键盘轴体对比 本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选? 阅读本文大约需要8分钟 目录 最近在学习单元测试的相关知识,在这里我将分享一下我在学习过程中,使用到的一些辅助工具或框架 ...
- android单元测试adapter,Android 单元测试
我在学习Android的时候最头特的就是 测试! 虚拟机慢有不舒服,真机也是觉得很慢,因为要频繁的安装应用,所以Android 单元测试的使用就显得尤为重要了. 想实现简单的单元测试不是很难,只要几步 ...
最新文章
- 微服务为什么一定要Zookeeper?
- Android设置ProgressBar的前景和背景及其在多线程中的刷新
- 实时通信RTC技术栈之:视频编解码
- m3u8 php防盗链代码,Nginx-accesskey权限模块使用——简单的m3u8防盗链
- D3 dataset - what is usage of key function in data
- oracle---函数(trunc,nvl,nvl2)
- LeetCode 303,560,1248 (前缀求和 )
- 使用statsvn统计svn中代码行
- acm康复训练记4-world final 2017
- Please Select android SDK的解决办法
- Mac查看占用端口进程
- ajax和jquery教程pdf,ajax和jquery教程pdf
- 大数据 (016)Hadoop-MR编程 -- 【使用hadoop计算微博用户可能喜欢的关键词----编程】
- 实现京东商城手机注册获取验证码
- 逆向基础——软件手动脱壳技术入门
- Windows系统服务器配置SSH服务
- 常用保险术语256条
- 【linux学习笔记】began,每次玩这个都特别着迷
- C++简单程序典型案例
- 人工智能前景和现状如何?AI发展趋势分析
热门文章
- 2018年软件评测师上午真题 + 答案(一)
- 如何进行产品功能设计
- pcb成型板aoi检测_【技术】7种PCB板常用检测方法
- iOS Xcode始终提示library not found for xxx的时候
- pandas神器操作excel表格大全(数据分析数据预处理)
- 中国又一家芯片制造厂崛起,强势进入全球前六,中国芯双骄争艳
- 【运筹优化】GA遗传算法求解TSP问题(Java实现)
- eclipse可以写前端吗_这是我写的情诗,情诗可以这样写吗?晒晒你写的情诗吧...
- 汉芯核心成员爆料:陈进与台湾公司秘密交易
- 拥有用于游戏的独立服务器的好处