Robolectric
1.0、参考
1.1、Robolectric简介
是什么 ?:a framework that brings fast and reliable unit tests to Android. Unlike traditional emulator-based Android tests, Robolectric tests run inside the JVM on your workstation in seconds.
开发语言:Java
官方主页:http://robolectric.org
源码仓库:https://github.com/robolectric/robolectric
1.2、Robolectric的优点 .vs 缺点

使用Robolectric API编写的测试用例是运行在PCJVM中的, 而不是运行在Android设备中的,所以,它的运行速度非常快。 但是,这也带来了新问题。我们先来看看为Android设备编写的代码, 现在要跑在PC(GNU/LinuxWindowsmacOS)上, 这带来了什么不同:

操作系统CPU架构动态库
Android

ARM mips

x86 x86_64

.so
GNU/Linuxx86 x86_64.so
Windowsx86 x86_64.dll
macOS        x86_64.dylib

从上面的对比图中也看出来了,如果您的Android App使用了so,那么就麻烦了:

如果这些so是您自己开发的,也就是您有这些so的源码,您当然也可以生成对应操作系统 可以运行的动态库,只是稍微麻烦了一些而已,但是还是能做到的。

如果您使用的so不是您自己开发的, 那么这些功能您将不能使用Robolectric进行测试,因为,毫无疑问,加载动态库的时候会崩溃。

总结:任何事物都具有两面性,事物本无优缺点,优点就是缺点,缺点也是优点,到底是优点还是缺点,看如何被使用。

Robolectric之所以不提供对加载动态库的支持,主要是因为这是个无底洞,需求太多,没法统一处理。 第二个原因是他们认为单元测试框架,不提供这个功能也说得过去,毕竟,它不是集成测试框架。但是,事实上, 我们大多数时候既用它做单元测试,也做一些集成测试的工作。所以,我们有必要自己做个支持。

1.3、生成PC上可以加载的动态

我们只要生成GNU/LinuxmacOS上的动态库即可, 不用生成Windows上的动态库,原因如下:

1、很多POSIX标准的函数在Windows系统中没有,很可能还要做适配,太麻烦。

2、Windows用户可以通过Docker运行这些单元测试。

3、大部分库都提供了linux_x86_64的库,GNU/Linux用户拿过来就可以使用,macOSWindows用户通过Docker运行这些单元测试。 完美解决问题。

预备条件:

1、GNU/Linux用户需要安装GCC, 如果你的源码有C++, 确保一定要安装g++

2、安装JDK,并设置好JAVA_HOME环境变量。

3、安装Android NDK,并设置好ANDROID_NDK_HOME环境变量。

环境变量示例

JAVA_HOME=$(/usr/libexec/java_home)
ANDROID_NDK_HOME=/usr/local/share/android-ndk
生成GNU/Linux上可以加载的.so:

GNU/LinuxTerminal中执行如下的脚本:

ANDROID_NDK_INCLUDE_ROOT=$ANDROID_NDK_HOME/sysroot/usr/include
FLAGS="-I$JAVA_HOME/include -I${ANDROID_NDK_INCLUDE_ROOT} -I${ANDROID_NDK_INCLUDE_ROOT}/x86_64-linux-android"

gcc -c $FLAGS *.c *.cpp
g++ -shared -undefined suppress -flat_namespace *.o -o libxx.so

这样就生成了libxx.so可以运行在GNU/Linux上的动态库了。

生成macOS上可以加载的.dylib:

macOSTerminal中执行如下的脚本:

ANDROID_NDK_INCLUDE_ROOT=$ANDROID_NDK_HOME/sysroot/usr/include
FLAGS="-I$JAVA_HOME/include -I${ANDROID_NDK_INCLUDE_ROOT} -I${ANDROID_NDK_INCLUDE_ROOT}/x86_64-linux-android"

gcc -c $FLAGS *.c *.cpp
g++ -dynamiclib -undefined suppress -flat_namespace *.o -o libxx.dylib

这样就生成了libxx.dylib可以运行在macOS上的动态库了。

1.4、Robolectric环境搭建

1、在app modulebuild.gradle脚本中加入如下配置:

android {
    testOptions {
        unitTests {
            includeAndroidResources = true
        }
    }
    sourceSets {
        test {
            jniLibs.srcDir(['src/main/libs'])
            java.srcDirs("src/test/kotlin")
        }
    }
}
dependencies {
    //https://github.com/junit-team/junit4
    testImplementation 'junit:junit:4.12'

    //https://github.com/robolectric/robolectric
    testImplementation 'org.robolectric:robolectric:4.3'
}

如果使用的是build.gradle.kts,脚本如下:

android {
    testOptions {
        unitTests.apply {
            isIncludeAndroidResources = true
        }
    }
    sourceSets {
        getByName("test") {
            jniLibs.srcDir("src/main/libs")
            java.srcDirs("src/test/kotlin")
        }
    }
}
dependencies {
    //https://github.com/junit-team/junit4
    testImplementation("junit:junit:4.12")

    //https://github.com/robolectric/robolectric
    testImplementation("org.robolectric:robolectric:4.3")
}

2、同步一下gradle配置

3、创建src/test/kotlin/com/fpliu/newton/test目录:

mkdir -p src/test/kotlin/com/fpliu/newton/test

4、创建测试用例类:

package com.fpliu.newton.test

import android.text.TextUtils

import com.fpliu.newton.log.Logger
import com.fpliu.newton.test.MyTestRunner
import com.hyzb.usercenter.BuildConfig
import com.fpliu.newton.test.util.RxJavaUtil
import com.hyzb.usercenter.HttpRequest
import com.hyzb.usercenter.entity.UserInfo

import org.junit.After
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.annotation.Config
import org.robolectric.shadows.ShadowLog


/**
 * 网络接口测试用例集
 * @author 792793182@qq.com 2017-04-13.
 */
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [23], application = RobolectricApplication::class)
class HttpApiTest {

    companion object {

        private val TAG = HttpApiTest::class.java.simpleName

        private const val USER_NAME = "15656059397"

        private const val PASSWORD = "123456"
    }

    @Before
    fun setup() {
        ShadowLog.stream = System.out
        Logger.i(TAG, "setup()")
        RxJavaUtil.initRxJava2()
    }

    @Test
    fun testLoginViaPassword() {
        HttpRequest
            .loginViaPassword(USER_NAME, PASSWORD, "")
            .subscribe({
                Assert.assertNotNull(it)

                val userInfo = it.data
                Assert.assertNotNull(userInfo)

                Logger.i(TAG, "userInfo = $userInfo")

                Assert.assertTrue("$UserInfo.id is empty!", !TextUtils.isEmpty(userInfo!!.id))
            }, { Logger.e(TAG, "", it) })
    }
}

5、如果你使用了.so,按照前面介绍的方法制作PC上的动态库,放到src/test/libs的对应目录下, 对用关系如下:

6、运行测试用例

1.5、运行测试用例
方法一  :  通过Android Studio运行测试用例

RunWith注解标注的类上或者在Test注解标注的方法上右击鼠标, 出现的右键菜单中选择“Run xx”,即可运行测试用例。

注意:首次运行测试用例的时候,会先下载一些资源,不要惊慌,等一下就好了。

运行的过程中可能会出现如下的错误:

java.lang.VerifyError: Expecting a stackmap frame at branch target 27

解决的办法是给JVM-noverify参数。

方法一  :  通过本机命令运行测试用例
./gradlew testDebugUnitTest
方法三  :  通过Docker运行测试用例

1、构建一个Android SDK Docker image

sudo docker build -t fpliu/android-env:1.0 https://raw.githubusercontent.com/leleliu008/auto/master/android/sdk/Dockerfile

2、使用镜像创建并运行容器:

sudo docker run --tty --interactive -v $(pwd):/android -w /android fpliu/android-env:1.0 ./gradlew testDebugUnitTest
1.6、测试报告

如果使用命令运行的测试用例,生成的报告以HTML格式存在,路径为app/build/reports/tests/testDebugUnitTest/index.html

如果使用Android Studio运行的测试用例,直接在界面中显示了结果。也可以导出结果。

更常见的做法是通过Jenkins自动运行单元测试, 将结果呈现在SonarQubeWeb Dashboard中, 团队成员统一review