参考:Android+jacoco实现代码覆盖率最正确的实现方式,没有之一! - 腾讯云开发者社区-腾讯云 (tencent.com)
Android ui 单元测试 覆盖率,Android单元测试/Ui测试+JaCoCo覆盖率统计_Microsoft俱乐部的博客-CSDN博客
java+uiautomator——APP自动化——背诵整理_小白龙白龙马的博客-CSDN博客
Android单元测试只看这一篇就够了_gf771115的博客-CSDN博客
一.环境配置
Android Studio IDE
../app/jacoco.gradle
apply plugin: 'jacoco'
jacoco {
toolVersion = "0.8.2"
}
android {
defaultPublishConfig "debug"
buildTypes {
debug {
/**打开覆盖率统计开关**/
testCoverageEnabled = true
}
}
}
//源代码路径,你有多少个module,你就在这写多少个路径
def coverageSourceDirs = [
'../app/src/main/java',
]
//class文件路径,就是我上面提到的class路径,看你的工程class生成路径是什么,替换我的就行
def coverageClassDirs = [
'../app/build/intermediates/javac/debug/classes',
]
//这个就是具体解析ec文件的任务,会根据我们指定的class路径、源码路径、ec路径进行解析输出
//如果没有自动生成 覆盖率报告的话, 可以执行这个task 手动生成
task jacocoTestReport(type: JacocoReport) {
group = "Reporting"
description = "Generate Jacoco coverage reports after running tests."
reports {
xml.enabled = true
html.enabled = true
}
classDirectories = files(files(coverageClassDirs).files.collect {
fileTree(dir: it,
// 过滤不需要统计的class文件
excludes: ['**/R*.class',
'**/*$InjectAdapter.class',
'**/*$ModuleAdapter.class',
'**/*$ViewInjector*.class',
'**/I*.class',
'**/*ForTest.class',
'**/MenuItemManager.class',
'**/AudioPictureSettingManager.class',
'**/audiocontrol/**/*.class',
'**/audiopicturesetting/**/*.class',
'**/bean/**/*.class',
'**/database/**/*.class'
])
})
sourceDirectories = files(coverageSourceDirs)
//设备不同 coverage.ec 的名字会发生变化
executionData = files("$buildDir/outputs/code_coverage/debugAndroidTest/connected/BRAVIA 4K AE1 - 12-coverage.ec")
}
../app/unittest.gradle
//覆盖率统计配置
apply from: 'jacoco.gradle'
//单元测试 配置
configurations.all {
resolutionStrategy {
force 'androidx.lifecycle:lifecycle-common:2.0.0'
force 'androidx.lifecycle:lifecycle-runtime:2.0.0'
force 'androidx.collection:collection:1.0.0'
}
}
dependencies {
//单元测试 junit
testImplementation 'junit:junit:4.13.2'
// Core library
androidTestImplementation 'androidx.test:core:1.4.0'//
// AndroidJUnitRunner and JUnit Rules
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test:rules:1.4.0'//
// Assertions
androidTestImplementation 'androidx.test.ext:junit:1.1.3'//
androidTestImplementation 'androidx.test.ext:truth:1.4.0'
androidTestImplementation 'com.google.truth:truth:1.0'
// Espresso dependencies
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.4.0'
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.4.0'
androidTestImplementation 'androidx.test.espresso:espresso-accessibility:3.4.0'
androidTestImplementation 'androidx.test.espresso:espresso-web:3.4.0'
androidTestImplementation 'androidx.test.espresso.idling:idling-concurrent:3.4.0'
// The following Espresso dependency can be either "implementation"
// or "androidTestImplementation", depending on whether you want the
// dependency to appear on your APK's compile classpath or the test APK
// classpath.
androidTestImplementation 'androidx.test.espresso:espresso-idling-resource:3.4.0'
// uiautomator
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'//
//单元测试 powermock
// testImplementation 'org.powermock:powermock-api-mockito2:1.7.4'
// testImplementation 'org.powermock:powermock-module-junit4:1.7.4'
}
../app/build.gradle
plugins {
id 'com.android.application'
id 'kotlin-android'
}
apply from: 'unittest.gradle'
android {...}
...
二. 实践
1.编写测试代码:
androidTest 中是测试android 代码的地方
..\app\src\androidTest
demo比较简单, 主要是测试AIDL 接口, 接口功能是弹出dialog, 所以要判断是否弹出dialog
MyTest.java
@RunWith(AndroidJUnit4.class)
public class MyTest {
long TIMEOUT = 10 * 1000;
public static final String TAG = "MyTAG";
@Rule
public final ServiceTestRule mServiceRule = new ServiceTestRule();
IMyService iService ; UiDevice device;
Context appContext;
CyclicBarrier barrier = new CyclicBarrier(2);
@Before
public void setUp() throws Exception {
appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
Intent serviceIntent = new Intent(ApplicationProvider.getApplicationContext(), MyService.class);
// Bind the service and grab a reference to the binder.
IBinder binder = mServiceRule.bindService(serviceIntent);
iService = IMyService.Stub.asInterface(binder); }
@After
public void tearDown() throws Exception {
mServiceRule.unbindService();
}
private void sleeSsecondsOf(int second) {
barrier.reset();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000 * second);
barrier.await();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
try {
barrier.await();
} catch (Exception e) {
e.printStackTrace();
}
}
@Category(CategoryTest.QuickCategory.class)
@Test
public void testShowDangbeiDialog() throws RemoteException {
iService.showDangbeiMessage("测试Dialog", "test");
sleeSsecondsOf(5);
boolean showing = false;
UiObject dialog = device.findObject(new UiSelector().textContains("测试Dialog"));
try {
Log.d(TAG, "testShowDangbeiDialog: " + dialog.getClassName());
showing = true;
} catch (UiObjectNotFoundException e) {
showing = false;
Log.d(TAG, "testShowDangbeiDialog: " + e.toString());
e.printStackTrace();
}
assertThat(showing, equalTo(true));
iService.removeDangbeiMessage(); sleeSsecondsOf(2);
dialog = device.findObject(new UiSelector().textContains("测试Dialog"));
try {
Log.d(TAG, "testShowDangbeiDialog: " + dialog.getClassName());
showing = true;
} catch (UiObjectNotFoundException e) {
showing = false;
Log.d(TAG, "testShowDangbeiDialog: " + e.toString());
e.printStackTrace();
}
assertThat(showing, equalTo(false));
}
}
2.运行测试代码
在Android Studio 右边栏 的Gradle 中双击Tasks/verification/createDebugCoverageReport 进行测试
运行完之后:
测试报告目录:..\app\build\reports\androidTests\connected\index.html
覆盖率目录: ..\app\build\reports\coverage\debug\index.html
在浏览器中打开即可查看
有时候覆盖率文件没有自动生成, 这个时候就需要用到上面 jacoco.gradle 中的task 了: Android Studio 下边栏 Terminal 中执行 gradlew jacocoTestReport
执行完毕后会覆盖率报告会在如下位置:
..\app\build\reports\jacoco\jacocoTestReport\html\index.html