是什么 ? | : | 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 |
使用Robolectric API
编写的测试用例是运行在PC
的JVM中的, 而不是运行在Android设备中的,所以,它的运行速度非常快
。 但是,这也带来了新问题。我们先来看看为Android设备编写的代码, 现在要跑在PC
(GNU/Linux、Windows、macOS)上, 这带来了什么不同:
操作系统 | CPU架构 | 动态库 |
---|---|---|
Android |
| .so |
GNU/Linux | x86 x86_64 | .so |
Windows | x86 x86_64 | .dll |
macOS | x86_64 | .dylib |
从上面的对比图中也看出来了,如果您的Android App
使用了so
,那么就麻烦了:
如果这些so
是您自己开发的,也就是您有这些so
的源码,您当然也可以生成对应操作系统 可以运行的动态库,只是稍微麻烦了一些而已,但是还是能做到的。
如果您使用的so
不是您自己开发的, 那么这些功能您将不能使用Robolectric
进行测试,因为,毫无疑问,加载动态库的时候会崩溃。
总结:任何事物都具有两面性,事物本无优缺点,优点就是缺点,缺点也是优点,到底是优点还是缺点,看如何被使用。
Robolectric
之所以不提供对加载动态库的支持,主要是因为这是个无底洞,需求太多,没法统一处理。 第二个原因是他们认为单元测试框架,不提供这个功能也说得过去,毕竟,它不是集成测试框架。但是,事实上, 我们大多数时候既用它做单元测试,也做一些集成测试的工作。所以,我们有必要自己做个支持。
我们只要生成GNU/Linux和macOS上的动态库即可, 不用生成Windows上的动态库,原因如下:
1、很多POSIX标准的函数在Windows系统中没有,很可能还要做适配,太麻烦。
2、Windows用户可以通过Docker运行这些单元测试。
3、大部分库都提供了linux_x86_64
的库,GNU/Linux用户拿过来就可以使用,macOS和Windows用户通过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
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上的动态库了。
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、在app module
的build.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、运行测试用例
在RunWith
注解标注的类上或者在Test
注解标注的方法上右击鼠标, 出现的右键菜单中选择“Run xx”,即可运行测试用例。
注意:首次运行测试用例的时候,会先下载一些资源,不要惊慌,等一下就好了。
运行的过程中可能会出现如下的错误:
java.lang.VerifyError: Expecting a stackmap frame at branch target 27
解决的办法是给JVM
加-noverify
参数。
./gradlew testDebugUnitTest
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
如果使用命令运行的测试用例,生成的报告以HTML
格式存在,路径为app/build/reports/tests/testDebugUnitTest/index.html
。
如果使用Android Studio运行的测试用例,直接在界面中显示了结果。也可以导出结果。
更常见的做法是通过Jenkins自动运行单元测试, 将结果呈现在SonarQube的Web Dashboard
中, 团队成员统一review
。