单元测试入门

如果你也在学单元测试,不妨看一下这几篇文章:

  1. 单元测试入门
  2. Junit学习实践
  3. AndroidX Test学习实践
  4. Mockito学习实践

咱们平时改代码时经常会有牵一发而动全身全身的情况,改完之后比较慌,不知道会引起什么问题,怕有考虑不全的情况,摁下这个问题那个问题又起来了,怎么避免这种情况呢?
考虑引入单元测试吧,它可以为我们的软件质量提供强有力的保证。

我们在单元测试中经常用的库有JUnitMockitoRobolectricEspresso

  • 如果你的测试对Android框架有依赖,最好使用Robolectric;
  • 如果你的测试对Android框架依赖性极小,或如果测试仅取决于你自己的对象,可以使用Mockito等;
测试金字塔

测试金字塔包含三类测试:小型测试,中型测试,大型测试。

  • 小型测试是指单元测试,用于验证应用的行为,一次验证一个类。
  • 中型测试是指集成测试,用户验证模块内堆栈之间的互动或相关模块之间的互动。
  • 大型测试是指端到端测试,用于验证跨越了应用的多个模块的用户操作流程。

通常建议各类测试占比为:小型测试占 70%,中型测试占20%,大型测试占10%。
除了这些测试还包括性能测试,monkey测试等。

1.基础知识

我们新建一个工程之后通常包含两个测试目录,androidTest和test目录

  • test目标主要包含运行本地的测试内容,比如单元测试,称为本地测试;
  • androidTest目录包括运行在真机或虚拟设备上的测试内容,例如集成测试,端到端测试以及其他一些只依赖JVM无法完成的测试,称为仪器测试。

以下内容建议使用单元测试:ViewModels或Presenters,数据层尤其是repositories,工具类等。
单元测试中要覆盖正常的case和边界case,比如网络错误,除以0等情况。
不要用单元测试验证不属于你的代码,比如framework或库的正确行为。
activities,fragments或services等系统入口,因为没有太多逻辑,所以不适合用单元测试验证。

屏幕UI测试包含用户交互行为,如点击,输入等,建议每个界面一个测试类。
用户交互测试验证用户界面跳转流程。

添加测试依赖:

dependencies {
  // Required -- JUnit 4 framework
  testImplementation "junit:junit:$jUnitVersion"
  // Optional -- Robolectric environment
  testImplementation "androidx.test:core:$androidXTestVersion"
  // Optional -- Mockito framework
  testImplementation "org.mockito:mockito-core:$mockitoVersion"
  // Optional -- mockito-kotlin
  testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
  // Optional -- Mockk framework
  testImplementation "io.mockk:mockk:$mockkVersion"
}

testImplementation为本地测试添加依赖,添加的依赖只在test目录可用。
androidTestImplementation为仪器测试添加依赖,添加的依赖只在androidTest目录可用 。
通过他们添加的依赖都不会添加到最终生成的apk中。
本地测试和仪器测试的区别是他们运行方式不同
本地测试运行在你开发设备的JVM上,不需要运行在虚拟机或物理设备上,这种方式运行速度快,但不一定真实可靠,因为它不能完全模拟真实环境。
仪器测试运行在真机或虚拟设备上,它更能反映真实的运行环境,但运行速度相对较慢

2.运行本地测试

运行本地测试的方式很多,找到一种自己习惯的就行,比如:右键单击测试文件,选择运行。

运行本地测试

图中的6个点均能运行本地测试,其中6的位置,右键点击某个方法,可只运行某个方法

本地测试

务必选中左上角的✔️和取消图标,这样能确保测试结果不会存在遗漏。

3.运行仪器测试

运行仪器测试

仪器测试的运行方式和本地测试不同,仪器测试需要运行在虚拟机或真机设备,点击上图中右侧的运行按钮或右键点击源文件选择运行均可。
本地测试和仪器测试的区别是是否需要使用Android 系统或Android framework能力。

4.创建本地测试

4.1创建本地测试文件

右键点击你要创建测试的方法,选择Generate > Test.


Generate > Test.png

create test.png

弹出创建测试的弹框,修改类名称,点击OK。


Choose Destination Directory

选择test目录,因为我们要创建本地测试。
StatisticsUtilsTest

打开默认创建的测试文件,接下来我们要创建测试方法了。

4.2创建测试方法

class StatisticsUtilsTest {

    @Test
    fun getActiveAndCompletedStats_noCompleted_returnsHundredZero() {

        // Create an active task (the false makes this active)
        val tasks = listOf<Task>(
            Task("title", "desc", isCompleted = false)
        )
        // Call your function
        val result = getActiveAndCompletedStats(tasks)

        // Check the result
        assertEquals(result.completedTasksPercent, 0f)
        assertEquals(result.activeTasksPercent, 100f)
    }
}

创建测试方法,添加@Test注解,添加测试内容,使用Junit断言进行测试。
接下来右键运行即可。
官方demo中还使用了Hamcrest来写出更宜读的测试代码,比如

// REPLACE
assertEquals(result.completedTasksPercent, 0f)
assertEquals(result.activeTasksPercent, 100f)

// WITH
assertThat(result.activeTasksPercent, `is`(100f))
assertThat(result.completedTasksPercent, `is`(0f))

后面应该会单独拿出一篇学习Hamcrest。

4.3 测试用例命名规则

subjectUnderTest_actionOrInput_resultState
subject under test 是要被测试的方法或类,比如:getActiveAndCompletedStats
接下来是动作或输入,比如:noCompleted
最后是期待的结果,比如:returnsHundredZero

5.使用AndroidX Test测试ViewModel

AndroidX Test后边应该会单独用一篇文章进行学习。
先创建ViewModel测试类文件,方法类似上边创建本地测试方式,这里仍然选择本地测试目录。
AndroidX Test库包含提供用来测试的系统组件比如Apllications和Activities的类和方法。
使用AndroidX Test需要遵循如下步骤:

  1. 添加AndroidX Test依赖。
  2. 添加 Robolectric依赖。
    testImplementation "androidx.test.ext:junit-ktx:$androidXTestExtKotlinRunnerVersion"
    testImplementation "androidx.test:core-ktx:$androidXTestCoreVersion"
    testImplementation "org.robolectric:robolectric:$robolectricVersion"
  1. 添加AndroidJunit4注解。
@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {
    // Test code
}

4、编写AndroidX 测试代码。

@Test
    fun addNewTask_setsNewTaskEvent() {

        // Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

        // When adding a new task
        tasksViewModel.addNewTask()

        // Then the new task event is triggered
        // TODO test LiveData
    }

可以通过ApplicationProvider.getApplicationContext()获取application context。
AndroidX 测试API在本地测试和仪器测试中均可正常运行。在仪器测试中,ApplicationProvider.getApplicationContext()会从虚拟机或真机中获取context,在本地测试中,它会使用模拟的Android环境获取。
Robolectric提供AndroidX Test在本地测试中使用的模拟Android环境。
@RunWith(AndroidJUnit4::class)注解的作用是什么?
它用来指定要运行的runner,test runner是一个运行测试的Junit组件。
接下来我们使用InstantTaskExecutorRule测试LiveData
InstantTaskExecutorRule是一个Junit Rule,它和注解@get:Rule一起使用,当你需要测试LiveData时,使用此Rule
确保你已添加依赖

testImplementation "androidx.arch.core:core-testing:$archTestingVersion"

在测试文件中添加

class TasksViewModelTest {
    @get:Rule
    var instantExecutorRule = InstantTaskExecutorRule()
    
    // Other code...
}

给LiveData添加观察者

@Test
fun addNewTask_setsNewTaskEvent() {

    // Given a fresh ViewModel
    val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())


    // Create observer - no need for it to do anything!
    val observer = Observer<Event<Unit>> {}
    try {

        // Observe the LiveData forever
        tasksViewModel.newTaskEvent.observeForever(observer)

        // When adding a new task
        tasksViewModel.addNewTask()

        // Then the new task event is triggered
        val value = tasksViewModel.newTaskEvent.value
        assertThat(value?.getContentIfNotHandled(), (not(nullValue())))

    } finally {
        // Whatever happens, don't forget to remove the observer!
        tasksViewModel.newTaskEvent.removeObserver(observer)
    }
}

单元测试入门基本就到这了,接下来应该还会有几篇文章和单元测试有关。

参考:

  1. Android单元测试只看这一篇就够了
  2. Android单元测试研究与实践
  3. 构建本地单元测试
  4. 测试应用的 Activity
  5. 测试应用的 Fragment
  6. mockito
  7. Robolectric
  8. JUnit
  9. Testing Basics Codelab
  10. Hamcrest
  11. Truth
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342