Android测试框架Robolectric和Espresso的集成和使用
Robolectric跑测试的时候并不需要将app安装到手机上,它能模拟Android的环境,并且测试运行的时间较短。
Espresso运行测试的时候需要安装app,并且运行的时间比较长,这更像是一种集成测试。
集成
依赖
testCompile "org.mockito:mockito-core:1.+"
testCompile "org.robolectric:shadows-multidex:3.0"
testCompile 'junit:junit:4.12'
testCompile 'org.robolectric:robolectric:3.3.2'
//比较unitTest结果是能给出具体的不同点
testCompile group: 'net.javacrumbs.json-unit', name: 'json-unit', version: '1.16.0'
// androidTestCompile 'com.android.support:support-annotations:24.0.0'
androidTestCompile ('com.android.support.test:runner:0.5', {
exclude group: 'com.android.support', module: 'support-annotations'
})
androidTestCompile ('com.android.support.test:rules:0.5', {
exclude group: 'com.android.support', module: 'support-annotations'
})
// Optional -- Hamcrest library
androidTestCompile 'org.hamcrest:hamcrest-library:1.3'
// Optional -- UI testing with Espresso
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
defaultConfig 中增加
defaultConfig {
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
testOptions 在android{}中
testOptions {
unitTests.returnDefaultValues = true
unitTests.all {
beforeTest {
def testClassDir = buildDir.getAbsolutePath() + "/intermediates/classes/test/debug"
copy {
from(android.sourceSets.test.java.srcDirs) {
exclude '**/*.java'
}
into(testClassDir)
}
}
}
}
集成查看覆盖率工具jacoco
apply plugin: 'jacoco'
jacoco {
toolVersion = "0.7.1.201405082137"
}
def coverageSourceDirs = [
'../hall/src/main/java'
]
task jacocoTestReport(type: JacocoReport, dependsOn: "testDebugUnitTest") {
group = "Reporting"
description = "Generate Jacoco coverage reports"
classDirectories = fileTree(
dir: '../hall/build/intermediates/classes/debug',
excludes: ['**/R.class',
'**/R$*.class',
'**/*$ViewInjector*.*',
'**/BuildConfig.*',
'**/Manifest*.*']
)
additionalSourceDirs = files(coverageSourceDirs)
sourceDirectories = files(coverageSourceDirs)
executionData = files('../hall/build/jacoco/testDebugUnitTest.exec')
reports {
xml.enabled = true
html.enabled = true
}
}
开始写测试代码
单元测试
SimpleTest.class
普通的Junit单元测试无法 对Android的数据库,SharedPreferences以及Assets文件的读取等进行测试。Robolectric能模拟Android环境,让这些单元测试变的很简单。
public class SimpleTest extends RoboBase{
Application application;
HallConfigManager mHallConfigManager;
HallDbManager mHallDbManager;
AppBean appBean;
//单元测试开始,需要的用到的变量在此初始化
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);//初始化Mockit
application = RuntimeEnvironment.application;
mHallConfigManager = HallConfigManager.getInstance();
mHallDbManager = HallDbManager.getInstance();
//需要mock的类初始化
appBean = Mockito.mock(AppBean.class);
}
//sharedPreferences测试
@Test
public void sharedPreferencesTest() throws Exception{
mHallConfigManager.setQQ("10086");
Assert.assertEquals("10086", mHallConfigManager.getQQ());
}
//数据库测试
@Test
public void dbTest() throws Exception{
AppBean appBean = new AppBean();
appBean.gamePackageName = "com.uc108.test";
mHallDbManager.replaceAppBean(appBean);
AppBean appTemp = mHallDbManager.getAppBean("com.uc108.test");
Assert.assertEquals("com.uc108.test", appTemp.gamePackageName);
}
//这条目前测试在AndroidStudio3.0上会运行失败,建议使用AndroidStudio2.2
//读取assets目录下的文件测试
@Test
public void assetsTest() throws Exception{
String str = PackageUtils.getAssetsString("defaultlocation.json");
Assert.assertEquals(str, "abc");
}
//mock测试,单元测试的过程中,可能会调用外部的方法A,为了让方法A返回值可控,这个时候你需要使用mock
@Test
public void mockTest() throws Exception{
appBean.setGameName("abc");
when(appBean.getGameName()).thenReturn("cde");
Assert.assertEquals("cde", appBean.getGameName());
}
}
defaultlocation.json
{
"ProvinceName":"",
"CityName":"",
"DistrictName":""
}
测试结果:
测试通过 sharedPreferencesTest,dbTest,mockTest
测试未通过assetsTest
初次运行依赖Robolectric的测试的时候,由于网络的原因,速度会特别慢,这个时候我们需要如下操作:把Robolectric下载依赖的地址 指向国内的源
MyRobolectricTestRunner.class
public class MyRobolectricTestRunner extends RobolectricTestRunner {
/**
* Creates a runner to run {@code testClass}. Looks in your working directory for your AndroidManifest.xml file
* and res directory by default. Use the {@link Config} annotation to configure.
*
* @param testClass the test class to be run
* @throws InitializationError if junit says so
*/
public MyRobolectricTestRunner(Class<?> testClass) throws InitializationError {
super(testClass);
RoboSettings.setMavenRepositoryId("alimaven");
RoboSettings.setMavenRepositoryUrl("http://maven.aliyun.com/nexus/content/groups/public/");
}
}
RobApplication.class
public class RobApplication extends HallApplication {
}
Espresso UI集成测试
这个测试需要把apk安装到手机后才能运行测试。
GameSearchActivityTest.class
@RunWith(AndroidJUnit4.class)
@LargeTest
public class GameSearchActivityTest {
//设置的测试的Activity
@Rule
public ActivityTestRule<GameSearchActivity> mActivityRule = new ActivityTestRule<>(
GameSearchActivity.class, true, true);
@Before
public void setUp() throws Exception {
Thread.sleep(2000);
}
@Test
public void onCreate() throws Exception {
}
@Test
public void onSearch() throws Exception {
//在输入框中输入doudizhu
onView(withId(R.id.search_et)).perform(typeText("doudizhu"), closeSoftKeyboard());
//点击搜索按钮
onView(withId(R.id.search_iv)).perform(click());
//匹配search_game_result_lv ListView中tv_name游戏标题为“斗地主”item进行点击
onData(allOf(instanceOf(AppBean.class), ItemMatcher.withName("斗地主")))
.inAdapterView(withId(R.id.search_game_result_lv))
.onChildView(withId(R.id.tv_name))
.perform(click());
}
public static class ItemMatcher {
public static Matcher<Object> withName(final String name) {
return new BoundedMatcher<Object,AppBean>(AppBean.class) {
@Override
public void describeTo(org.hamcrest.Description description) {
description.appendText("item mathcher");
}
@Override
protected boolean matchesSafely(AppBean item) {
if(item != null && !TextUtils.isEmpty(item.getGameName()) && item.getGameName().contains(name)) {
return true;
}
return false;
}
};
}
}
Android测试框架可以使用@SmallTest,@MediumTest和@LargeTest 来标注测试方法,这些分类划分主要是根据测试访问数据的位置,如本地,SD卡,网络,下表为通常划分测试等级的基本方法:
Feature | Small | Medium | Large |
---|---|---|---|
Network access | No | localhost only | Yes |
Database | No | Yes | Yes |
File system access | No | Yes | Yes |
Use external systems | No | Discouraged | Yes |
Multiple threads | No | Yes | Yes |
Sleep statements | No | Yes | Yes |
System properties | No | Yes | Yes |
Time limit (seconds) | 60 | 300 | 900+ |