美文网首页测试
Android单元测试(一)-基础

Android单元测试(一)-基础

作者: Stan_Z | 来源:发表于2021-04-30 16:07 被阅读0次

    一、什么是单元测试

    单元测试是测试某个类的某个方法能否正常工作的一种手段。

    二、单元测试目的

    • 验收(改动和重构)
    • 快速验证逻辑
    • 优化代码设计

    三、Android单元测试工具链

    google推荐:junit4+mockito+rebolectric
    最佳实战选择:junit4+mockito+powermock+rebolectric

    工具介绍:

    • Junit4 Java单元测试框架。
    • Mockito 针对Java的mocking框架,解决测试类对其他类的依赖问题。
    • Powermock mockito功能的延伸,且完美兼容mockito。
    • Rebolectric 模拟android接口,让单测跑在jvm上,去掉对android环境的依赖。

    注:
    mockito功能有点弱,很多功能受限。powermock可以跟mockito兼容而且功能更强。与mockito比,支持修改和mock静态类或对象的私有方法/成员,还支持很多反射方法,非常实用。

    四、如何在Android项目中运行单元测试

    案例:

    public class SimpleClass {
       public int add(int a, int b) {
           return a + b;
       }
    }
    
    public class SimpleClassTest {
       @Test
       public void testAdd() {
           SimpleClass simpleClass = new SimpleClass();
           int sum = simpleClass.add(1, 2);
           Assert.assertEquals(3, sum);
       }
    }
    

    新建单元测试:

    运行:

    运行结果

    (验证通过):

    (验证失败):

    查看覆盖率:

    五、单测基本规范与验收标准

    代码规范:

    • 测试类命名:被测类名+Test;
    • 测试类被测方法命名:test+首字母大写的被测方法名,如果一个被测方法有多个测试用例,则在其后添加“_+序号”;
    • 测试类必现有setup()、cleanup()方法,用于初始化和释放资源;
    • 测试用例必现有断言。

    验收标准:

    • 代码覆盖率:行覆盖,函数覆盖,分支覆盖。目前google、facebook等国外一线大厂单测覆盖率要求是82%左右。

    六、单测框架及其使用

    6.1 JUnit4

    JUnit4 :控制执行顺序 + 验证结果

    控制执行顺序:

    注解名 含义
    @Test 表示此方法为测试方法
    @before 每个测试方法前执行,可做初始化操作
    @After 在每个测试方法后执行,可做释放资源操作
    @Ignore 忽略的测试方法
    @BeforeClass 在类中所有方法前运行。此注解修饰的方法必须是static void
    @AfterClass 在类中最后运行。此注解修饰的方法必须是static void
    @RunWith 指定该测试类使用某个运行器
    @Parameters 指定测试类的测试数据集合
    @Rule 重新制定测试类中方法的行为
    @FixMethodOrder 指定测试类中方法的执行顺序

    验证结果:

    1)Assert

    方法名 方法描述
    assertNotEquals 断言传入的预期值与实际值是不相等的
    assertNotEquals 断言传入的预期值与实际值是不相等的
    assertArrayEquals 断言传入的预期数组与实际数组是相等的
    assertNull 断言传入的对象是为空
    assertNotNull 断言传入的对象是不为空
    assertTrue 断言条件为真
    assertFalse 断言条件为假
    assertSame 断言两个对象引用同一个对象,相当于“==”
    assertNotSame 断言两个对象引用不同的对象,相当于“!=”
    assertThat 断言实际值是否满足指定的条件

    2)Assert.assertThat 作用跟Assert类似。

    匹配器 说明 例子
    is 断言参数等于后面给出的匹配表达式
    not 断言参数不等于后面给出的匹配表达式
    equalTo 断言参数相等
    equalToIgnoringCase 断言字符串相等忽略大小写
    containsString 断言字符串包含某字符串
    startsWith 断言字符串以某字符串开始
    endsWith 断言字符串以某字符串结束
    nullValue 断言参数的值为null
    notNullValue 断言参数的值不为null
    greaterThan 断言参数大于
    lessThan 断言参数小于
    greaterThanOrEqualTo 断言参数大于等于
    lessThanOrEqualTo 断言参数小于等于
    closeTo 断言浮点型数在某一范围内
    allOf 断言符合所有条件,相当于&&
    anyOf 断言符合某一条件,相当于或
    hasKey 断言Map集合含有此键
    hasValue 断言Map集合含有此值
    hasItem 断言迭代对象含有此元素

    举例:

    public class Junit4Example {
       public int plus(int a, int b) {
           return a + b;
       }
    }
    
    public class Junit4ExampleTest {
       @BeforeClass
       public static void beforeClass() throws Exception {
       }
    
       @Before
       public void setUp() throws Exception {
       }
    
        //两种验证写法:
        //Assert
       @Test
       public void testPlus_1() {
           Junit4Example junit4Example = new Junit4Example();
           Assert.assertEquals(3, junit4Example.plus(1, 2));
       }
    
        //assertThat
       @Test
       public void testPlus_2() {
           Junit4Example junit4Example = new Junit4Example();
           Assert.assertThat(3, equalTo(junit4Example.plus(1, 2)));
       }
    
       @After
       public void tearDown() throws Exception {
       }
    
       @AfterClass
       public static void afterClass() throws Exception {
       }
    }
    

    生命周期:

    com.stan.androidtest.Junit4ExampleTest beforeClass
    com.stan.androidtest.Junit4ExampleTest setUp
    com.stan.androidtest.Junit4ExampleTest testPlus_1
    com.stan.androidtest.Junit4ExampleTest tearDown
    com.stan.androidtest.Junit4ExampleTest setUp
    com.stan.androidtest.Junit4ExampleTest testPlus_2
    com.stan.androidtest.Junit4ExampleTest tearDown
    com.stan.androidtest.Junit4ExampleTest afterClass
    
    6.2 Mockito

    Mock的作用:解决测试类对其他类的依赖问题。Mock的类所有方法都是空,所有变量都是初始值。
    Mockito 使用操作主要分如下三步:
    mock/spy对象 + 打桩 + 验证行为

    mock/spy对象
    区别:
    mock: 所有方法都是空方法,非void方法都将返回默认值,比如int方法返回0,对象方法将返回null,而void方法将什么都不做。
    spy:跟正常类对象一样,是正常对象的替身。

    适用场景
    mock:类对外部依赖较多,只关新少数函数的具体实现;
    spy:跟mock相反,类对外依赖较少,关心大部分函数的具体实现。

    打桩

    方法名 方法描述
    thenReturn(T value) 设置要返回的值
    thenThrow(Throwable… throwables) 设置要抛出的异常
    thenAnswer(Answer<?> answer) 对结果进行拦截
    doReturn(Object toBeReturned) 提前设置要返回的值
    doThrow(Throwable… toBeThrown) 提前设置要抛出的异常
    doAnswer(Answer answer) 提前对结果进行拦截
    doCallRealMethod() 调用某一个方法的真实实现
    doNothing() 设置void方法什么也不做

    验证行为

    方法名 方法描述
    after(long millis) 在给定的时间后进行验证
    timeout(long millis) 验证方法执行是否超时
    atLeast(int minNumberOfInvocations) 至少进行n次验证
    atMost(int maxNumberOfInvocations) 至多进行n次验证
    description(String description) 验证失败时输出的内容
    times(int wantedNumberOfInvocations) 验证调用方法的次数
    never() 验证交互没有发生,相当于times(0)
    only() 验证方法只被调用一次,相当于times(1)

    参数匹配

    方法名 方法描述
    anyObject() 匹配任何对象
    any(Class<T> 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 <T> matcher) 创建自定义的参数匹配模式

    其他方法

    方法名 方法描述
    reset(T … mocks) 重置Mock
    spy(Class<T> classToSpy) 实现调用真实对象的实现
    inOrder(Object… mocks) 验证执行顺序
    @InjectMocks注解 自动将模拟对象注入到被测试对象中

    Mockito局限性:不支持private方法、匿名类、final类、static方法。

    举例:

    Mockito 常规操作:mock/spy对象 + 打桩 + 验证行为
    
    mock对象 + 打桩配合:
    
    mock + doCallRealMethod()
    
    //mock当前类的空实现
    
    MockitoExample example = Mockito.mock(MockitoExample.class);
    
    //让类的空方法恢复成真实实现
    
    Mockito.doCallRealMethod().when(example).plus(Mockito.anyInt(), Mockito.anyInt());
    
    spy + doNothing()/doReturn()
    
    //spy当前类的真实实现
    
    MockitoExample example = Mockito.spy(MockitoExample.class);
    
    //让某些方法不执行真实逻辑
    
    //有返回值的
    
    Mockito.doReturn(0).when(example).function(Mockito.anyInt());
    
    //没返回值的
    
    Mockito.doNothing().when(example).function(Mockito.anyInt());
    
    验证行为:
    
    //验证方法被调用
    
    Mockito.verify(example, Mockito.times(1)).function(1);
    
    //验证方法执行结果
    
    Assert.assertEquals(3, example.function(1, 2));
    
    特殊验证:
    
    //一个参数确定,另一个是匹配器
    
    Mockito.verify(example, Mockito.times(1)).function(Mockito.eq(1),Mockito.anyInt());
    
    这里确定参数不能直接写,需要eq包一下。
    
    //匹配非基础类型
    
    Mockito.verify(example, Mockito.times(1)).function(Mockito.any(类名.class));
    
    6.3 PowerMockito

    PowerMock是Mockito的扩展增强版,支持mock private、static、final方法和类,还增加了很多反射方法可以方便修改静态和非静态成员等。功能比Mockito增加很多。

    @RunWith(PowerMockRunner.class)//使用PowerMockRunner时是兼容Mockito的,Mockito中的各种方法也是可以正常使用到
    @PrepareForTest()//要mock的类需要写在这个注解里
    public class PowerMockitoExampleTest {
        @Test
       public void testGetPrivateField() {
       ...
       }
    }
    

    针对对象操作

    功能 实现
    读取对象私有成员 WhiteBox.getInternalState
    修改对象私有成员 WhiteBox.setInternalState 或 MemberModifier.field
    verify对象私有方法 PowerMockito.verifyPrivate
    Invoke对象私有方法 Whitebox.invokeMethod
    修改对象私有函数 PowerMockito.replace
    调用私有构造方法 Whitebox.invokeConstructor
    跳过方法执行 PowerMockito.suppress

    针对静态类

    功能 实现
    读取对象私有成员 WhiteBox.getInternalState
    修改对象私有成员 WhiteBox.setInternalState
    调用静态私有方法 Whitebox.invokeMethod
    替换静态函数 PowerMockito.replace
    verify公有静态方法 PowerMockito.verifyStatic
    verify私有静态方法 PowerMockito.verifyPrivate

    针对final类

    mock final类举例:

    public final class FinalClassA {
       public final String finalMethod() {
           return "finalMethod";
       }
    }
    
    @Test
    public final void mockPowerFinalClassTest() {
           FinalClassA instance = new FinalClassA();
           FinalClassA mock = PowerMockito.mock(FinalClassA.class);
           Mockito.when(mock.finalMethod()).thenReturn("mock method");
           Assert.assertNotEquals(instance.finalMethod(), mock.finalMethod());
    }
    

    当然PowerMockito也不是万能的,它目前解决不了匿名类的场景。

    七、测试框架-Robolectric

    单测运行在真机或模拟器上,跑起来非常耗时,而且依赖于Android环境不是很方便。如果直接运行在JVM就方便很多,但是直接运行在JVM,代码依赖的Android SDK的api 这些接口在android.jar中,获取不到就会抛RuntimeException(“stub!!”)。

    Robolectric作为一个测试框架,通过实现一套JVM能运行的Android代码,然后在unit test运行的时候去截取android相关的代码调用,然后转到他们的他们实现的代码去执行这个调用的过程。解决了上面的问题。

    @RunWith(RobolectricTestRunner.class)  //Robolectric需要使用RobolectricTestRunner这个runner
    @Config(constants = BuildConfig.class, application = ApplicationStub.class, sdk = 21)
    @PowerMockIgnore({ "org.mockito.*", "org.robolectric.*", "android.*" }) //PowerMock有自己的ClassLoader,叫做MockClassLoader,Robolectric也有自己的ClassLoader,SandBoxClassLoader,如果一个类被两个加载了,使用时会报错。这里对一些类不要PowerMock,避免冲突
    @PrepareForTest(StaticClass.class)
    public class MyTest {
       //这个rule一定要有,解决powermock和robolectric兼容性
       @Rule
       public PowerMockRule rule = new PowerMockRule();
    ...
    }
    

    相关文章

      网友评论

        本文标题:Android单元测试(一)-基础

        本文链接:https://www.haomeiwen.com/subject/ntgnrltx.html