本篇緊接着上一篇的內容,對MVP + Dagger進行單元測試。Dagger的部分可以參看 Dagger2與AndroidInjector
1.相關實現代碼
首先添加Dagger所需的依賴:
compile 'com.google.dagger:dagger:2.13'
compile 'com.google.dagger:dagger-android:2.13'
compile 'com.google.dagger:dagger-android-support:2.13'
annotationProcessor 'com.google.dagger:dagger-compiler:2.13'
annotationProcessor 'com.google.dagger:dagger-android-processor:2.13'
例子還是使用登錄場景的獲取驗證碼與登錄。與上篇重複的代碼我就忽略了。
封裝Dagger的Avtivity : BaseMVPDaggerActivity
public abstract class BaseMVPDaggerActivity<V extends MvpView, T extends BaseMVPPresenter<V>> extends DaggerAppCompatActivity implements MvpView {
@Inject
protected T mPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPresenter.attachView((V)this);
mProgress = new ProgressDialog(this);
}
...
}
BaseMVPPresenter
與MvpView
不變。
GithubApi
的初始化。
@Module
public class ClientModule {
@Singleton
@Provides
GithubApi provideGithubApi(Retrofit retrofit){
return retrofit.create(GithubApi.class);
}
@Singleton
@Provides
Retrofit provideRetrofit(Retrofit.Builder builder, OkHttpClient client) {
return builder
.baseUrl(HttpUrl.parse(GithubApi.BASE_URL))
.client(client)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build();
}
@Singleton
@Provides
OkHttpClient provideClient(OkHttpClient.Builder okHttpClient, LoggingInterceptor loggingInterceptor) {
OkHttpClient.Builder builder = okHttpClient.addInterceptor(loggingInterceptor);
return builder.build();
}
/**
* 打印信息的攔截器
* @return 攔截器
*/
@Singleton
@Provides
LoggingInterceptor provideLoggingInterceptor() {
return new LoggingInterceptor();
}
@Singleton
@Provides
Retrofit.Builder provideRetrofitBuilder() {
return new Retrofit.Builder();
}
@Singleton
@Provides
OkHttpClient.Builder provideClientBuilder() {
return new OkHttpClient.Builder();
}
}
有了ClientModule
,我們就可以輕鬆地獲取到GithubApi
來隨時隨地的調用網絡接口了。
自定義MyApp
繼承DaggerApplication
。
public class MyApp extends DaggerApplication {
private static MyApp instance;
@Inject
GithubApi mGithubApi;
@Override
public void onCreate() {
super.onCreate();
if (instance == null){
instance = this;
}
}
public static MyApp getInstance() {
return instance;
}
//直接獲取,便於測試
public GithubApi getGithubApi(){
return mGithubApi;
}
@Override
protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
return DaggerAppComponent.builder().create(this);
}
}
如果你不想在自定義Application中寫這些便於測試的get方法,可以單獨寫出來,使用Robolectric
的@Config
來單獨指定自定義Application。
LoginDaggerActivity
基本沒有變化,只是不用初始化LoginDaggerPresenter
了,可以直接使用了。因爲Dagger已經幫我們自動創建好了。
public class LoginDaggerActivity extends BaseMVPDaggerActivity<LoginMvpView, LoginDaggerPresenter> implements LoginMvpView, View.OnClickListener{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
mPresenter.getIdentify(); //<--直接調用
....
}
....其餘不變
}
LoginDaggerPresenter
變化也不大。只需要構造方法注入,Dagger就會自動幫我們注入我們需要的GithubApi
。實際中,對於SP、數據庫的操作都可以這樣去寫。
@ActivityScope
public class LoginDaggerPresenter extends BaseMVPPresenter<LoginMvpView> {
private GithubApi mApi;
@Inject
public LoginDaggerPresenter(GithubApi mApi){
this.mApi = mApi;
}
....其餘不變
}
最後AppComponent
中統一注入各個Module。
@Module
public abstract class BuildersModule {
@ActivityScope
@ContributesAndroidInjector
abstract LoginDaggerActivity loginDaggerActivityInjector();
}
@Singleton
@Component(modules = {
AppModule.class,
ClientModule.class,
BuildersModule.class,
AndroidSupportInjectionModule.class})
public interface AppComponent extends AndroidInjector<MyApp> {
@Component.Builder
abstract class Builder extends AndroidInjector.Builder<MyApp> {}
}
到這裏,實現的代碼基本就完了。下面我們來測試它。
2.單元測試代碼
Activity
的部分不變,有點小變動的主要是Presenter
的測試部分。
@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 23)
public class LoginDaggerPresenterTest {
private LoginDaggerPresenter mPresenter;
@Mock
private LoginMvpView mvpView;
@Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();
@Before
public void setUp(){
ShadowLog.stream = System.out;
//<---實例化Presenter
mPresenter = new LoginDaggerPresenter(MyApp.getInstance().getGithubApi());
mPresenter.attachView(mvpView);
}
....其餘不變
}
可以看出來加入了Dagger後的變化並不是很大,測試起來還是很方便的。
3.使用Jacoco
之前有人在github上問我,有沒有使用jacoco生成測試報告。其實使用起來並不是很麻煩。
新建一個jacoco.gradle
文件,並輸入以下內容:
apply plugin: 'jacoco'
jacoco {
toolVersion = "0.8.0" //指定jacoco的版本
reportsDir = file("$buildDir/JacocoReport") //指定jacoco生成報告的文件夾
}
android {
buildTypes {
debug {
//打開覆蓋率統計開關
testCoverageEnabled = true
}
}
}
//依賴於testDebugUnitTest任務
task jacocoTestReport(type: JacocoReport, dependsOn: 'testDebugUnitTest') {
group = "reporting" //指定task的分組
reports {
xml.enabled = true //開啓xml報告
html.enabled = true //開啓html報告
}
def debugTree = fileTree(dir: "${buildDir}/intermediates/classes/debug/com/zl/weilu/androidut", //指定類文件夾
includes: ["**/*Presenter.*"], //包含類的規則,這裏我們生成所有Presenter類的測試報告
excludes: ['**/R.class',
'**/R$*.class',
'**/BuildConfig.*',
'**/Manifest*.*']) //排除類的規則
def mainSrc = "${project.projectDir}/src/main/java" //指定源碼目錄
sourceDirectories = files([mainSrc])
classDirectories = files([debugTree])
executionData = files("${buildDir}/jacoco/testDebugUnitTest.exec") //指定報告數據的路徑
}
然後在應用Module下的build.gradle
文件中引用剛新建的gradle
文件:
apply from: 'jacoco.gradle'
sync項目之後可以在任務列表看到新增加的任務:
其中testDebugUnitTest
任務會生成單元測試結果報告,包含xml及html格式分別對應test-results
和reports
文件夾;jacocoTestReport
任務會生成單元測試覆蓋率報告,結果存放在jacoco
和JacocoReport
文件夾。以上的文件都在對應Module的build
文件下。
本項目的測試報告如下:
代碼覆蓋率如下:
詳細的內容我們可以在生成的html頁面中點擊查看。
PS:如果有亂碼問題,可以使用gradlew -Dfile.encoding=UTF-8 jacocoTestReport
來解決
使用前:
使用後:
紅色表示未測試部分,綠色表示測試部分,還有一種黃色表示部分測試,比如if else的部分分支被執行。
4.參考
本篇的內容不是很多,但是涵蓋的東西卻是之前的總和。掌握了這些,一般場景下的單元測試你就已經得心應手了。到這裏,單元測試的內容基本就結束了。後面我會查漏補缺一下,看有沒有遺漏的地方需要補充。就這樣了。。。。
所有代碼已上傳至Github。希望大家多多點贊支持!