android之单元测试
之所以写单元测试文章是怕自己学的东西忘了,因为我已经在开始遗忘了,一个人做任何事情都是有理由的,吃饭是为了活着,喜欢一个人是因为他的某方面打动了我,所以事出必有因,但不排除任何事都是有两面的,说不定我刚说的话也是不全面不对的,只是暂时我是相信它的,废话不多说,先记录:
- 为什么要做单元测试
刚开始老大让我去看单元测试相关的文章,我连单元测试是啥都不知道,不过我记得在大学时学测试的时候老师教的白盒黑盒以及取极限值等乱七八糟的东西,后来也就想着看看吧,网上有很多的文章讲为什么要单元测试和它的重要性,但是单元测试到底是啥,单元是啥我是没看到直接答案的。
单元测试中测试是指检测出代码中的bug,用户体验不和谐,页面变形等一些不美好的影响用户体验的乱七八糟的问题,单元是指以类为单位,以方法为单元,一个类一个方法的去测试,这个单元我认为是指类中的某一具体方法。当你发现你的code无法单元测试的时候说明你的代码违背了单一职责的原则。 - 单元测试搭配
开发环境:android studio 3.0 , com.android.tools.build:gradle:3.0.0
测试依赖库:
dependencies {
compile ‘org.robolectric:robolectric:3.5.1’
compile ‘org.mockito:mockito-core:2.11.0’
compile ‘org.jmockit:jmockit:1.32’
compile ‘junit:junit:4.12’
}
接下来打开以下网址并阅读一遍:
http://robolectric.org robolectric官网
https://github.com/robolectric/robolectric/issues/ robolectric框架测试中遇到的问题,此处有答案,搜索即可
http://junit.org/junit4/javadoc/latest/index.html junit的java文档
http://static.javadoc.io/org.mockito/mockito-core/2.11.0/org/mockito/Mockito.html mockito官网
为什么要用junit:这是单元测试的根本,纯java可能就不需要其他框架支撑了,其他单元测试框架都是基于它的,这是测java代码的。
为什么要用robolectric:安卓相关的东西都是跑在安卓系统上的,就像html都是跑在浏览器一样,脱离了环境就需要其它条件支撑,真机运行慢,使用它不需要跑真机,直接右键运行code就可以出结果非常快,和java版右键运行类似,此框架主要是吧涉及到安卓相关的东西替换成了它自己的东西,它写了一些影子类是模仿的andorid的原生类,比如ShadowTextView,功能和原生TextView一样,但运行的时候不用跑真机。
为什么要用mockito:如果你的项目里有些对象是涉及到so了的,robolectric貌似加载不了so,要加载的自己制作一个特殊的so吧,so都是涉及底层的,生成不了但是为了流程走通,不妨碍到其他单元测试,那么就可以用这个框架来生成这个对象,灰常方便。
- 具体用法和遇到的坑
问题一:robolectric这个框架它可以模拟安卓原生类它是用到了我们写的一些资源文件的,它引入的路径就是build下的,res下的资源,mainfest文件,assets资源,当源码用到这些资源时,robolectric就会去build目录下去找这些资源然后使用,所以当你跑单元测试的时候先给robolectric配置资源路径,否则测试会通过不了。
问题二:
MaskEvent是包访问权限,这里不方便创建这个对象于是使用mockito模仿一个对象出来让流程跑通看是否能达到预期的测试结果
其他的等我学的多点的时候再来记,记得看那四个网址。
mockito基本用法
package cn.efrobot.speech;
import com.efrobot.library.test.BaseTest;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
/**
* Created by zhangyun on 2017/11/15.
*
* @author ZhangYun
* @version 1.0
*/
public class LearnTest extends BaseTest {
@Test
public void learnTest1() {
//模拟创建一个对象,当对象和so相关的时候无法new出来就可以使用,或者测试当前类,但当前类包含其他类对象,我们不关心这个对象的操作的时候可以模拟一个对象
List mockedList = mock(List.class);
mockedList.add("one");
mockedList.clear();
//确认是否添加了一个one数据操作,确认是否调用了clear操作
verify(mockedList).add("one");
verify(mockedList).clear();
}
@Test
public void learnTest2() {
//新建一个LinkedList类的对象
LinkedList mockedList1 = mock(LinkedList.class);
//当调用list获取0号数据时,返回first结果
when(mockedList1.get(0)).thenReturn("first");
//当调用list获取1号数据时,抛出一个运行时异常
when(mockedList1.get(1)).thenThrow(new RuntimeException());
System.out.println(mockedList1.get(0));//打印出first
// System.out.println(mockedList1.get(1));//抛出一个异常
System.out.println(mockedList1.get(999));//
//验证之前list已经调用过get0
verify(mockedList1).get(0);
}
@Test
public void learnTest3() {
List mockedList = mock(List.class);
//当get任何一个int类型值时返回element
when(mockedList.get(anyInt())).thenReturn("element");
System.out.println(mockedList.get(999));
verify(mockedList).get(anyInt());
//当个get任何一个key时返回值都是123
Map<String, String> it = mock(Map.class);
when(it.get(anyString())).thenReturn("123");
System.out.println(it.get("1"));
}
@Test
public void learnTest4() {
//新建一个LinkedList对象
LinkedList mockedList = mock(LinkedList.class);
mockedList.add("once");
mockedList.add("twice");
mockedList.add("twice");
mockedList.add("three times");
mockedList.add("three times");
mockedList.add("three times");
//验证是否执行了添加数据"once"的操作
verify(mockedList).add("once");
//验证添加once数据只执行了一次
verify(mockedList, times(1)).add("once");
//验证添加once数据执行了2次,添加three times执行了3次
verify(mockedList, times(2)).add("twice");
verify(mockedList, times(3)).add("three times");
//验证添加数据never happened 从来没有执行过
verify(mockedList, never()).add("never happened");
//验证添加数据three times至少调用一次,three times最多调用5次
verify(mockedList, atLeastOnce()).add("three times");
verify(mockedList, atLeast(2)).add("three times");
verify(mockedList, atMost(3)).add("three times");
}
@Test
public void learnTest5() {
LinkedList mockedList = mock(LinkedList.class);
//当list调用clear的时候抛出运行时异常
doThrow(new RuntimeException()).when(mockedList).clear();
doThrow(new RuntimeException()).when(mockedList).getFirst();
//以下抛出运行时异常:
mockedList.clear();
}
// @Test
// public void learnTest6(){
//
// // 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
// }
@Test
public void learnTest7() {
LinkedList mockOne = mock(LinkedList.class);
LinkedList mockTwo = mock(LinkedList.class);
LinkedList mockThree = mock(LinkedList.class);
mockOne.add("one");
//验证添加了数据one
verify(mockOne).add("one");
//验证从来没有添加数据two
verify(mockOne, never()).add("two");
//验证mockTwo和mockThree这两个数组啥操作都木有做过,比如你把mockTwo添加一个数据试试,会报错哦^-^
verifyZeroInteractions(mockTwo, mockThree);
}
@Test
public void learnTest8() {
LinkedList mockedList = mock(LinkedList.class);
mockedList.add("one");
mockedList.add("two");
verify(mockedList).add("one");
//验证没有更多的交互,和verifyZeroInteractions功能相似
verifyNoMoreInteractions(mockedList);
}
@Test
public void learnTest9() {
LinkedList mockOne = mock(LinkedList.class);
when(mockOne.get(0))
.thenThrow(new RuntimeException())
.thenReturn("foo");
//第一次获取返回值的时候返回一个运行时异常,第二次调用的时候返回foo,第三第四第N返回值为最后一个
mockOne.get(0);
//打印foo
System.out.println(mockOne.get(0));
//打印foo
System.out.println(mockOne.get(0));
// when(mock.someMethod("some arg")).thenReturn("one", "two", "three"); 简单点调用
}
@Test
public void learnTest10() {
Map<String, String> it = mock(Map.class);
when(it.get(anyString())).thenAnswer(new Answer() {
public Object answer(InvocationOnMock invocation) {
Object[] args = invocation.getArguments();
Object mock = invocation.getMock();
return "called with arguments: " + args;
}
});
//the following prints "called with arguments: foo"
System.out.println(it.get("foo"));
}
@Test
public void learnTest11() {
LinkedList mockedList = mock(LinkedList.class);
//doReturn()| doThrow()| doAnswer()| doNothing()| doCallRealMethod()方法家族
doThrow(new RuntimeException()).when(mockedList).clear();
//following throws RuntimeException:
mockedList.clear();
}
@Test
public void learnTest12() {
List list = mock(LinkedList.class);
List spy = mock(LinkedList.class);
//Impossible: real method is called so spy.get(0) throws IndexOutOfBoundsException (the list is yet empty)
when(list.get(0)).thenReturn("foo");
System.out.print(list.get(0));
//You have to use doReturn() for stubbing
doReturn("foo").when(spy).get(0);
System.out.print(spy.get(0));
}
@Test
public void learnTest13() {
//使用spy可以创建一个局部的对象
List list = spy(new LinkedList());
//mock一个对象
Map mock = mock(Map.class);
//可以调用这个对象的真实实现,这里调用会报错因为get方法在map类中是抽象的不是被实现了的方法
when(mock.get("key")).thenCallRealMethod();
}
@Test
public void learnTest14() {
List mock = mock(List.class);
when(mock.get(0)).thenReturn("测试");
//在给定的时间范围内去调用某个方法
verify(mock, timeout(100)).get(0);
//调用一次get0时要在100毫秒内完成
verify(mock, timeout(100).times(1)).get(0);
//调用二次get0时要在100毫秒内完成
verify(mock, timeout(100).times(2)).get(0);
//在100毫秒以内至少被调用2次
verify(mock, timeout(100).atLeast(2)).get(0);
}
}
Junit的基本用法
package cn.efrobot.speech;
import com.efrobot.library.test.BaseTest;
import org.hamcrest.core.CombinableMatcher;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import java.util.Arrays;
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.anyOf;
import static org.hamcrest.CoreMatchers.both;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.everyItem;
import static org.hamcrest.CoreMatchers.hasItems;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
/**
* Created by zhangyun on 2017/11/16.
*
* @author ZhangYun
* @version 1.0
*/
public class JunitLearnTest extends BaseTest {
@Test
public void learnTest1() {
//判断2个byte数组是否相等
byte[] expected = "trial".getBytes();
byte[] actual = "trial".getBytes();
assertArrayEquals("failure - byte arrays not same", expected, actual);
//判断此2个object是否相等
assertEquals("failure - strings are not equal", "text", "text");
//判断括号里的条件是否为false
assertFalse("failure - should be false", false);
//判断括号里的条件是否为true
assertTrue("failure - should be true", true);
//判断括号里的条件是否为空
assertNotNull("should not be null", new Object());
//判断这2个object不是指向相同的引用
assertNotSame("should not be same Object", new Object(), new Object());
//判断括号里的参数是否为空
assertNull("should be null", null);
//判断这2个object是指向相同的引用
Integer aNumber = Integer.valueOf(768);
assertSame("should be same", aNumber, aNumber);
//判断albumen既包含a又包含b
assertThat("albumen", both(containsString("a")).and(containsString("b")));
//判断list列表里面包含item one和three
assertThat(Arrays.asList("one", "two", "three"), hasItems("one", "three"));
//判断数组的每一个item中都包含n字符
assertThat(Arrays.asList(new String[]{"fun", "ban", "net"}), everyItem(containsString("n")));
//1,比较good这个字符串是以good开头,并且等于good
assertThat("good", allOf(equalTo("good"), startsWith("good")));
//2,比较good这个字符串,不等于bad 或者不等于good
assertThat("good", not(allOf(equalTo("bad"), equalTo("good"))));
//3,比较good这个字符串,等于bad或者等于good
assertThat("good", anyOf(equalTo("bad"), equalTo("good")));
//4,比较7这个数字不等于3或者不等于4
assertThat(7, not(CombinableMatcher.<Integer>either(equalTo(3)).or(equalTo(4))));
//5,比较第一个参数object和后面的第二个参数object不是同一个对象
assertThat(new Object(), not(sameInstance(new Object())));
}
@RunWith(Suite.class)
@Suite.SuiteClasses({
FeatureTestSuite2.class,
FeatureTestSuite.class})
public class AllTestClass {
//运行AllTestClass这个类就可以吧test1和test2全部测试了
}
public static class FeatureTestSuite {
@Test
public void test1() {
System.out.print("1111");
}
}
public static class FeatureTestSuite2 {
@Test
public void test2() {
System.out.print("22222");
}
}
}