Android 单元测试常见方案-笔记

文章思路

  1. 为什么要进行单元测试
- 提高稳定性,明确是否正确完成开发
- 快速反馈bug ,跑一遍单元测试用例,定位 bug
- 最小化技术债偿还,越往后修复 bug 的代价会越大
- 为代码重构提供安全保障,优化代码不用担心回归问题
  1. 单元测试要测什么
- 列出想要测试覆盖的正常、异常情况,进行测试验证
- 性能测试,例如算法的耗时等
  1. android 单元测试分为本地测试和 Instrumented 测试
- 本地测试,
路径: module-name/src/test/java
特点:无需设备支持,速度快。不可测试包含android 系统API 代码

- Instrumented 
路径:module-name/src/androidTest/java
特点:需运行设备,可使用android 系统API
  1. 项目代码分类
1. 强依赖组件关系,如 Activity、Service 等组件,与生命周期相关,无法进行单元测试,可以进行 Espresso 等UI 测试
2. 部分依赖,代码实现依赖注入,可使用 Mock 进行模拟 Context 等 android 对象的依赖注入
3. 纯Java 代码,不存在对 android 库的依赖,可以进行 JUnit 单元测试
  1. 常用测试框架及选择
单元测试
├── 是否依赖 android 库 
        ├── 否 -> 
            ├── 是否需要 mock 
                ├── 否 -> JUnit
                ├── 是 -> JUnit + Mock
        ├── 是
            ├── 是否可用 mock
                ├── 是 ->  JUnit + Mock
                ├── 否
                    ├── AndroidJunitRunner + Mock
  1. 测试框架例子
Android 单元测试实例代码

## JUnit4 单元测试
1. CalculatorTest 单元测试例子
2. CalculatorWithParameterTest ,多个参数覆盖测试
3. UnitTestSuite ,运行多个单元测试的测试集合

## AndroidJunit 单元测试
1. SharedPreferencesHelperTest。 带Mock 的测试 android api 例子
  1. Mockito 实现原理,涉及 cglib 库
- Mockito 本质是个 Proxy 代理模式的应用
- Proxy 代理,是给对象提供一个 Proxy 对象,所有对真实对象的调用,都会先经过 proxy 对象,然后 proxy 对象确定自己处理还是调用真实对象来处理
- Mockito 本质上是代理对象调用方法前,用 stub 方式设置其返回值,然后在真实调用时,用代理对象返回预设的返回值

常用测试框架实例

1. JUnit4 简单测试例子

/** [1] 自动生成待测试类。右键 generate -> Test, 选择待测试方法,勾选 setUp 方法
 * Author: hc
 * DATE: 2019-09-18 = 09:27
 */
class CalculatorTest {
    private lateinit var mCalculator: Calculator

    @Before
    fun setUp() {
        mCalculator = Calculator()
    }

    /**
     * [2] 最简单测试例子
     */
    @Test
    fun addTwoNumbers() {
        var addTwoNumbers = mCalculator.addTwoNumbers(1, 2)
        assert(addTwoNumbers == 3) {
            "addTwoNumbers is error"
        }
    }
}

2. JUnit4 多个参数

/** [1] 测试多个覆盖条件
 * 0. 右键,generate -> parameter funtion ,生成参数化数据提供方法
 * 1. 测试类 添加 @RunWith(Parameterized.class) 注解
 * 2. 添加构造函数,并将测试的参数作为其构造参数
 * 3. 新建测试方法 addTwoNumbers2,使用构造方法的传入的参数进行测试
 * Author: hc
 * DATE: 2019-09-18 = 09:27
 */
@RunWith(Parameterized::class)
class CalculatorWithParameterTest(val addA: Int, val addB: Int, val exceptV: Int) {
    //=========================================================================================
    //================================================ add in 2019-09-18 : 测试覆盖条件
    //=========================================================================================
    companion object {
        @JvmStatic
        @Parameterized.Parameters
        fun data(): Collection<Array<Any>> {
            return Arrays.asList(
                arrayOf<Any>(0, 0, 0),
                arrayOf<Any>(0, -1, -1),
                arrayOf<Any>(2, 2, 5),//x
                arrayOf<Any>(8, 8, 16),
                arrayOf<Any>(16, 16, 33),//x
                arrayOf<Any>(32, 0, 32),
                arrayOf<Any>(64, 64, 129)//x
            )
        }
    }

    private lateinit var mCalculator: Calculator

    @Before
    fun setUp() {
        mCalculator = Calculator()
    }

    /**
     * [2] 最简单测试例子
     */
    @Test
    fun addTwoNumbers2() {
        var addTwoNumbers = mCalculator.addTwoNumbers(addA, addB)

        assert(addTwoNumbers == exceptV) {
            "addTwoNumbers is error"
        }
    }
}

3. JUnit4 一次性执行多个测试用例

/**
 * [1] 注解配置总统套房类,并添加待运行的测试类,即可一次性统一运行
 * Author: hc
 * DATE: 2019-09-18 = 09:35
 */
@RunWith(Suite::class)
@Suite.SuiteClasses(CalculatorTest::class, CalculatorWithParameterTest::class)
class UnitTestSuite

4. AndroidJunit+ Mock 测试SP 存读

/**
 * Mock 例子
 * Author: hc
 * DATE: 2019-09-18 = 09:46
 */
@RunWith(AndroidJUnit4::class)
class SharedPreferencesHelperTest {
    lateinit var mContext: Context
    lateinit var mSharePreferences: SharedPreferences
    lateinit var sharePreferenceHelper: SharePreferenceHelper

    @Before
    fun setUp() {
        mContext = InstrumentationRegistry.getTargetContext()

        // [1] PreferenceManager.getDefaultSharedPreferences 与 mContext.getSharedPreferences 逻辑类似
        // 区别就是文件名默认是 "包名_preferences"
        mSharePreferences = PreferenceManager.getDefaultSharedPreferences(mContext)
//        mSharePreferences = mContext.getSharedPreferences(SharePreferenceHelper.NAME, Context.MODE_PRIVATE)


        //[2] mock 出场
        //2.1 mock 两个类
        var mockSharedPref = Mockito.mock(SharedPreferences::class.java)
        var mockEditor = Mockito.mock(SharedPreferences.Editor::class.java)
        // 2.2 mock 已经声明的对象
//        var mockEditor2 = Mockito.spy(mockEditor)

        //[3]
        // 3.1 条件, 调用 mockSharedPref.edit -> mockEditor
        //条件,调用 mockEditor.commit -> false
        org.mockito.Mockito.`when`(mockSharedPref.edit()).thenReturn(mockEditor)
        `when`(mockEditor.commit()).thenReturn(true)
        `when`(mockSharedPref.getString(SharePreferenceHelper.KEY_NAME, "")).thenReturn("myname")
        // 3.2 进阶,使用 Matchers.anyString() 替换固定字符串,其他类型以此类推
        `when`(mockSharedPref.getString(Matchers.anyString(), "")).thenReturn("myname")

        //[4] 埋点完毕,可以使用了
        sharePreferenceHelper = SharePreferenceHelper(mSharePreferences)
    }

    @Test
    public fun testSave() {
        var saveBean = sharePreferenceHelper.saveBean(KeyBean("1", "12"))
        assert(saveBean)
    }

    @Test
    public fun testPut() {
        var bean = sharePreferenceHelper.getBean()
        assert(bean.name == "myname")
    }
}

链接

  • github 代码: https://github.com/haichaoyuan/unitTestStudy
  • 参考: https://www.jianshu.com/p/925191464389
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值