当前位置:   article > 正文

【Android】主流单元测试组件,一网打尽!_android 单元测试插件

android 单元测试插件
概述

Android的测试的组件比较多,像JUnit,Espresso,UiAutomator,Mockito

Android的测试范围也包括很多种,比如Java代码测试,Android逻辑测试,AndroidUI测试

每个框架的侧重点各有不同,这里我们专门来介绍前三个Google官方推荐的测试框架

有些测试组件是只在AndroidTest中可用的,在JavaTest目录下如果访问不了,不用大惊小怪

Dependency
testApplicationId "com.android.unit.test"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

testOptions {
  	unitTests {
  			includeAndroidResources = true
  	}
}

useLibrary "android.test.runner"
useLibrary "android.test.base"
useLibrary "android.test.mock"
    
implementation "androidx.annotation:annotation:+"
testImplementation "junit:junit:+"
androidTestImplementation "androidx.test:core:+"
androidTestImplementation "androidx.test:core-ktx:+"
androidTestImplementation "androidx.test.ext:junit:+"
androidTestImplementation "androidx.test.ext:junit-ktx:+"
androidTestImplementation 'androidx.test:runner:+'
androidTestImplementation 'androidx.test:rules:+'
androidTestImplementation "androidx.test.espresso:espresso-core:+"
androidTestImplementation "androidx.test.uiautomator:uiautomator:+"
androidTestImplementation "org.hamcrest:hamcrest-integration:+"
androidTestImplementation "com.google.truth:truth:+"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
JUnit

@RunWith指定这是一个单元测试程序,并指定测试组件

@Test指定要进行测试的函数

@Before和@After指定@Test前后的准备和清理工作

@BeforeClass和@AfterClass指定进程启动结束时的准备和清理工作

assert用来判断结果是否符合预期,所有assert全部通过代表该单元测试用例通过

点击方法左侧的Run按钮,可以测试当前用例

点击类名左侧的Run按钮,可以测试所有用例

点击Run with Coverage,可以测试并统计通过率

import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4

@RunWith(JUnit4::class)
class BasicTest {

    var arg1 = 1

    var arg2 = 2

    var result = 3

    @Test
    fun test() {
        Assert.assertEquals(arg1 + arg2, result)
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
JUnit with Parameterized

Parameterized组件允许提供多组测试数据,对测试用例进行测试

测试数据通过@Parameterized.Parameters标注的静态方法来创建

import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized

@RunWith(Parameterized::class)
class ParameterizedTest {

    @JvmField
    @Parameterized.Parameter(0)
    var arg1 = 0

    @JvmField
    @Parameterized.Parameter(1)
    var arg2 = 0

    @JvmField
    @Parameterized.Parameter(2)
    var result = 0

    // offer groups of test data
    companion object {
        @JvmStatic
        @Parameterized.Parameters
        fun data(): Iterable<Array<Any>> {
            return listOf(
                arrayOf(0, 0, 0),
                arrayOf(1, 1, 2),
                arrayOf(3, 2, 5),
                arrayOf(4, 3, 7)
            )
        }
    }

    @Test
    @Throws(InterruptedException::class)
    fun add() {
        Assert.assertEquals(arg1 + arg2, result)
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
JUnit with Android Components

由于单元测试是不经过Application启动流程的

如果我们想通过Context去启动Activity,或者获取某些权限的话,就无法直接获取了

此时我们可以通过androidx.test组件提供的InstrumentationRegistry和GrantPermissionRule类来实现

import android.Manifest
import android.os.Environment
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.GrantPermissionRule
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import java.io.File
import java.io.FileOutputStream

@RunWith(AndroidJUnit4::class)
class AndroidTest {

    val appContext = InstrumentationRegistry.getInstrumentation().targetContext

    @get:Rule
    var rule: GrantPermissionRule = GrantPermissionRule.grant(
        Manifest.permission.READ_EXTERNAL_STORAGE,
        Manifest.permission.WRITE_EXTERNAL_STORAGE
    )

    @Test
    fun createFile() {
        val dir = appContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
        val file = File(dir, "x.png")
        val fos = FileOutputStream(file)
        fos.use {
            it.write(byteArrayOf(1, 2, 3, 4, 5))
        }
        val length = file.length()
        System.out.println(length)
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
AndroidTest的弊端

AndroidTest在执行完测试方法后,就会立刻退出测试进程

所以,AndroidTest只适合做一些基本的逻辑验证和UI验证工作,不适合长期运行Activity

如果一定要在AndroidTest中持续运行Activity的话,也有个投机取巧的方法

那就是让目标Activity运行在独立进程,启动Activity后让测试方法一直sleep,这样测试进程就不会退出

但是一般不建议这么去做,AndroidTest本身是很耗性能的

如果长期运行UI,再添加断点调试的话,时常会发生应用阻塞和退出的情况,达不到预期的效果

Instrument Test with Espresso

Instrument Test指的是仪器测试,在真机上进行测试的意思

在Android上,仪器测试主要是指UI测试,最常用的框架是Espresso和UiAutomator

Espresso可以模拟界面输入点击,验证处理结果是否符合预期

下面以一个简单的加法计算器,来演示Espresso的使用方法

测试流程为,向a输入框和b输入框输入数字,点击按钮,将a和b相加,结果存入c输入框

判断成功的标准为,获取c输入框的值,和指定值相等

package com.android.code

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.android.code.databinding.ActivityHomeBinding

class HomeActivity : AppCompatActivity() {

    private lateinit var binding: ActivityHomeBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityHomeBinding.inflate(layoutInflater)
        setContentView(binding.root)
        addEvents()
    }

    private fun addEvents() {
        binding.button.setOnClickListener {
            val a = binding.aEdit.text.toString().toInt()
            val b = binding.bEdit.text.toString().toInt()
            val c = (a + b).toString()
            binding.cEdit.setText(c)
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

Espresso的核心类主要有三个:ViewMatchers,ViewActions,ViewAssertions

分别用来查找符合条件的View,对View进行操作,判断View的属性是否符合预期

import android.app.Application
import androidx.lifecycle.Lifecycle
import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.Espresso
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.assertion.ViewAssertions
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.code.HomeActivity
import com.android.code.R
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class InstrumentTest {

    private lateinit var scenario: ActivityScenario<HomeActivity>

    @Before
    fun launchActivity() {
        scenario = ActivityScenario.launch(HomeActivity::class.java)
    }

    @Test
    fun sumEditText() {
        // get context
        val context = ApplicationProvider.getApplicationContext<Application>()
        // listener activity launch
        scenario.onActivity { activity ->
            assertEquals(activity.lifecycle.currentState, Lifecycle.State.RESUMED)
            activity.window.decorView.background = context.resources.getDrawable(R.drawable.ic)
        }
        // input a
        Espresso.onView(
            ViewMatchers.withId(R.id.aEdit)
        ).perform(
            ViewActions.typeText("1"), ViewActions.closeSoftKeyboard()
        )
        // input b
        Espresso.onView(
            ViewMatchers.withId(R.id.bEdit)
        ).perform(
            ViewActions.typeText("2"), ViewActions.closeSoftKeyboard()
        )
        // sum a and b
        Espresso.onView(
            ViewMatchers.withId(R.id.button)
        ).perform(
            ViewActions.click()
        )
        // assert c
        Espresso.onView(
            ViewMatchers.withId(R.id.cEdit)
        ).check(
            ViewAssertions.matches(
                ViewMatchers.withText("3")
            )
        )
        // prevent test thread from exit
        Thread.sleep(100 * 1000L)
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
Instrument Test with UiAutomator

UiAutomator和Espresso定位有所区别

Espresso是白盒测试,可以直接控制应用内的操作行为

UiAutomator是黑盒测试,站在设备的角度,对所有APP进行调度,但无法控制APP的内部逻辑

这里同样以加法计算器的例子来演示UiAutomator的用法

UiAutomator的三个核心类是UiDevice,UiObject,UiSelector

UiDevice表示安卓手机设备

UiObject表示控件元素

UiSelector表示控件元素的查找条件

UiAutomator没有提供专门的Assertion类,通过JUnit或GoogleTruth等提供的方法来断言都可

import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiSelector
import com.android.code.HomeActivity
import com.google.common.truth.Truth
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class InstrumentTest {

    private lateinit var scenario: ActivityScenario<HomeActivity>

    private lateinit var uiDevice: UiDevice

    @Before
    fun launchActivity() {
        scenario = ActivityScenario.launch(HomeActivity::class.java)
        uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
    }

    @Test
    fun sumEditText() {
        // direct to message app
        uiDevice.pressHome()
        val messageApp = uiDevice.findObject(
            UiSelector().text("Messages")
        )
        messageApp.clickAndWaitForNewWindow()
        // return to code app
        uiDevice.pressHome()
        val codeApp = uiDevice.findObject(
            UiSelector().text("Code")
        )
        codeApp.clickAndWaitForNewWindow()
        // input a
        val aEdit = uiDevice.findObject(
            UiSelector().resourceId("com.android.code:id/aEdit")
        )
        aEdit.setText("1")
        // input b
        val bEdit = uiDevice.findObject(
            UiSelector().resourceId("com.android.code:id/bEdit")
        )
        bEdit.setText("2")
        // click sum button
        val button = uiDevice.findObject(
            UiSelector().resourceId("com.android.code:id/button")
        )
        button.click()
        // assert c by google-truth
        val cEdit = uiDevice.findObject(
            UiSelector().resourceId("com.android.code:id/cEdit")
        )
        Truth.assertThat(cEdit.text).isEqualTo("3")
        // prevent test thread from exit
        Thread.sleep(3 * 1000L)
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
尾言

OK,安卓常用的测试套件基本就这些了

还有很多第三方的测试套件,原理基本都差不多

更细的使用技巧,就需要大家在实践中去掌握了

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/591386
推荐阅读
相关标签
  

闽ICP备14008679号