Open cyrushine opened 1 year ago
https://www.jianshu.com/p/2bf4ecd1752b
TDD
70%的小型测试:单元测试。对应到Android开发中是指本地单元测试(执行本地的JVM 如 Mockito)或者依赖测试模拟的Android环境进行单元测试(如Robolectric)。
20%的中型测试:集成测试. 对应到Android 开发中 就是使用Espresso 链接真机模拟真实操作
10%的大型测试:端对端测试。对应到Android开发中,就是使用如Google提供的 Firebase 测试实验室 在云端进行大规模测试你的应用,会通过验证不同的机型环境下你的应用是否能够正常运行。如在腾讯中,还会有录屏等功能分析视频中的帧数,校验元素的间距是否正常。往往是通过插桩等手段进行监控。
有依赖外部输入请保证外部输出的正确性和稳定性。 因此,对于网络请求和数据库相关的单元测试一般都会想办法转化成内存级别的输入输出。对于网络和数据库相关的单测请再自己模块进行测试,保证业务和组件库的隔离。
测试替代本质就是合理的隔离外部依赖,提高测试的正确性和速度
dependencies { testImplementation 'junit:junit:4.13.2' testImplementation "org.hamcrest:hamcrest-all:1.3" testImplementation "org.mockito:mockito-core:5.3.0" testImplementation "org.robolectric:robolectric:4.10" }
用 hamcrest 的匹配器(Matcher)拓展断言
hamcrest
assertThat(52, allOf(lessThan(60), greaterThan(45))); // pass // 断言 52 小于 60 且大于 45
构造 mock 对象的两种方式
SharedPreferences.Editor mockedEditor = mock(SharedPreferences.Editor.class); @RunWith(MockitoJUnitRunner.class) public class ExampleUnitTest { @Mock private SharedPreferences.Editor mockedEditor; }
实现 mock 对象
SharedPreferences.Editor mockedEditor = mock(SharedPreferences.Editor.class); // 定义方法的行为 when(mockedEditor.putBoolean(anyString(), anyBoolean())).thenThrow(new IllegalArgumentException()); when(mockedEditor.remove(anyString())).thenReturn(mockedEditor); when(mockedEditor.commit()).thenReturn(true, false, true, false); // 测试上面的方法定义是否正确 assertThrows(IllegalArgumentException.class, () -> mockedEditor.putBoolean("key", true)); assertEquals(mockedEditor.remove("key"), mockedEditor); assertTrue(mockedEditor.commit()); assertFalse(mockedEditor.commit()); // mock 出来的对象,如果方法没有被定义,则是空方法,返回默认值:null、0、false 等 assertNull(mockedEditor.putInt("key", 6)); // when-then 和 given-will 作用一样 given(mockedEditor.putInt(anyString(), anyInt())).willThrow(new NullPointerException()); assertThrows(NullPointerException.class, () -> mockedEditor.putInt("key", 34));
通过 spy 修改类/对象的行为
List<Integer> adds = new ArrayList<>(); adds.add(8); adds.add(5); adds.add(2); List<Integer> spy = Mockito.spy(new ArrayList<>()); doThrow(new IllegalArgumentException()).when(spy).add(anyInt()); // 其他方法不受影响,mock 对象是空方法 assertEquals(spy.size(), 0); spy.addAll(adds); assertEquals(spy.size(), adds.size()); // 当调用 add(int) 是抛出异常 assertThrows(IllegalArgumentException.class, () -> spy.add(7));
verify 验证方法调用次数和参数
verify
SharedPreferences.Editor mock = Mockito.mock(SharedPreferences.Editor.class); String key = "key", value = "value"; mock.putString(key, value); verify(mock, times(1)).putString(key, value); verify(mock, atLeast(1)).putString(key, value);
对于一个单元测试来说,链接真机的场景进行测试一般是大型测试需要模拟真实环境才需要的。或者说进行ui元素相关的校验才需要的测试。而绝大部分的测试都没有要求到ui元素校验,大多只是为了校验业务数据的是否正确。而这部分业务的校验依赖了android 系统的环境导致不能不链接真机/虚拟机。
而这种中小型的测试占了50%以上的情况都需要链接真机/虚拟机就太过浪费时间了,那么有没有办法在本地进行android测试呢?
如果使用Local测试,需要保证测试过程中不会调用Android系统API,否则会抛出RuntimeException异常,因为Local测试是直接跑在本机JVM的,而之所以我们能使用Android系统API,是因为编译的时候,我们依赖了一个名为“android.jar”的jar包,但是jar包里所有方法都是直接抛出了一个RuntimeException,是没有任何任何实现的,这只是Android为了我们能通过编译提供的一个Stub!当APP运行在真实的Android系统的时候,由于类加载机制,会加载位于framework的具有真正实现的类。由于我们的Local是直接在PC上运行的,所以调用这些系统API便会出错。
那么问题来了,我们既要使用Local测试,但测试过程又难免遇到调用系统API那怎么办?其中一个方法就是mock objects,比如借助Mockito,另外一种方式就是使用Robolectric, Robolectric就是为解决这个问题而生的。它实现一套JVM能运行的Android代码,然后在unit test运行的时候去截取android相关的代码调用,然后转到他们的他们实现的Shadow代码去执行这个调用的过程
https://www.jianshu.com/p/2bf4ecd1752b
优势和必要性
TDD
)的方式。其含义是把迭代中每一个应用视为一系列的模块, 在开发设计每一个功能时候,就先编写一个测试,然后不断的添加断言在其中,在编写的设计的过程中同时考虑到隔离性正确性测试金字塔
70%的小型测试:单元测试。对应到Android开发中是指本地单元测试(执行本地的JVM 如 Mockito)或者依赖测试模拟的Android环境进行单元测试(如Robolectric)。
20%的中型测试:集成测试. 对应到Android 开发中 就是使用Espresso 链接真机模拟真实操作
10%的大型测试:端对端测试。对应到Android开发中,就是使用如Google提供的 Firebase 测试实验室 在云端进行大规模测试你的应用,会通过验证不同的机型环境下你的应用是否能够正常运行。如在腾讯中,还会有录屏等功能分析视频中的帧数,校验元素的间距是否正常。往往是通过插桩等手段进行监控。
测试替代(Test Doubles)
测试替代本质就是合理的隔离外部依赖,提高测试的正确性和速度
常用的库
JUnit
用
hamcrest
的匹配器(Matcher)拓展断言Mockito
构造 mock 对象的两种方式
实现 mock 对象
通过 spy 修改类/对象的行为
verify
验证方法调用次数和参数robolectric
对于一个单元测试来说,链接真机的场景进行测试一般是大型测试需要模拟真实环境才需要的。或者说进行ui元素相关的校验才需要的测试。而绝大部分的测试都没有要求到ui元素校验,大多只是为了校验业务数据的是否正确。而这部分业务的校验依赖了android 系统的环境导致不能不链接真机/虚拟机。
而这种中小型的测试占了50%以上的情况都需要链接真机/虚拟机就太过浪费时间了,那么有没有办法在本地进行android测试呢?
如果使用Local测试,需要保证测试过程中不会调用Android系统API,否则会抛出RuntimeException异常,因为Local测试是直接跑在本机JVM的,而之所以我们能使用Android系统API,是因为编译的时候,我们依赖了一个名为“android.jar”的jar包,但是jar包里所有方法都是直接抛出了一个RuntimeException,是没有任何任何实现的,这只是Android为了我们能通过编译提供的一个Stub!当APP运行在真实的Android系统的时候,由于类加载机制,会加载位于framework的具有真正实现的类。由于我们的Local是直接在PC上运行的,所以调用这些系统API便会出错。
那么问题来了,我们既要使用Local测试,但测试过程又难免遇到调用系统API那怎么办?其中一个方法就是mock objects,比如借助Mockito,另外一种方式就是使用Robolectric, Robolectric就是为解决这个问题而生的。它实现一套JVM能运行的Android代码,然后在unit test运行的时候去截取android相关的代码调用,然后转到他们的他们实现的Shadow代码去执行这个调用的过程