使用 robolectric 做單元測試


簡述

上文android 如何開始測試提到了測試的基本知識,我也是初次接觸測試,可能存在誤解,如果各位發現,還請不吝指正。

基本介紹

如果你對Robolectric不夠了解,可以先閱讀以下鏈接:

  1. Robolectric 官網指南
  2. Robolectric GitHub
  3. Robolectric 2.4 升級 3.0對比WIKI
  4. Robolectric GitBook
  5. 用Robolectric來做Android unit testing
  6. Robolectric Github 官方示例
  7. Robolectric Github 其他開發者提供的示例
  8. Robolectric Github 在開源項目Android-Boilerplate中運用

常見問題

  1. 異步任務
  2. 如何使用內置的shadow對象
  3. 准備測試環境
  4. 測試時,Application 對象獲取為 null。(見下方項目實踐
  5. 測試時,需要有選擇的在Application中初始化代碼。見下方項目實踐
    后續遇到其他問題,再進行補充

項目實踐

遇到的第一個問題:如何有選擇的在 Application 初始化?

通常一段 App 的初始化代碼如下。

public class App extends Application{

// ignore some code

@Override
public void onCreate () {
super.onCreate ();
if (isMainProcess ()) {
setupCrashReporting ();
setupUmeng ();
setupBasic ();
setupComponent ();
}
}

// ignore some code
}

但是:

  • 如果使用setupUmeng友盟的一系列服務,可能會導致測試過程中衍伸出一些不必要的錯誤警告。
  • 若在App中實現了Thread.UncaughtExceptionHandler接口,來完成錯誤收集功能。則會導致單元測試時,無法正常執行。
  • 亦或者,需要在單元測試時初始化一些正常運行時額外的一些服務。

但是通常,直接更改App會導致代碼中的職責界限不清晰。

我選擇的做法是在 src/test/java目錄下擴展App子類UnitTestApp如下。

public class UnitTestApp extends App {

@Override
public void setupCrashReporting () {

}

@Override
public void setupUmeng () {

}

@Override
protected void setupComponent () {
CollectInfoUtil.init (this);
initVersionInfo ();
}

@Override
public boolean isMainProcess () {
return true;
}
}

這生產了一個僅測試使用的UnitTestApp,但是如何測試時使用的是UnitTestApp,而不是App。你需要自定義TestRunner,如下:

public class UnitTestGradleTestRunner extends RobolectricGradleTestRunner {

// This value should be changed as soon as Robolectric will support newer api.
private static final int SDK_EMULATE_LEVEL = 21;

public UnitTestGradleTestRunner (Class<?> klass) throws InitializationError {
super (klass);
}

@Override
public Config getConfig (Method method) {
final Config defaultConfig = super.getConfig (method);
return new Config.Implementation (
new int[]{SDK_EMULATE_LEVEL},
defaultConfig.manifest (),
defaultConfig.qualifiers (),
defaultConfig.packageName (),
defaultConfig.resourceDir (),
defaultConfig.assetDir (),
defaultConfig.shadows (),
UnitTestApp.class, // Here is the trick, we change application class to one with mocks.
defaultConfig.libraries (),
defaultConfig.constants () == Void.class ? BuildConfig.class : defaultConfig.constants ()
);
}

@NonNull
public static UnitTestApp getApp () {
return (UnitTestApp) RuntimeEnvironment.application;
}
}

在你需要使用到App的測試類中指定如下:

@RunWith (UnitTestGradleTestRunner.class)
@Config (constants = BuildConfig.class)
public class LoginActivityTest
{

@Before
public void setUp () throws Exception {
UnitTestApp app = UnitTestGradleTestRunner.getApp ();
Assert.assertNotNull ("shadowApp not null.", app);
Assert.assertNotNull ("shadowApp context not null.", app.getApplicationContext ());
}

}

其他建議

在測試過程中,經常會需要對測試代碼進行 set/get操作。但是大部分時候,一些方法或者域是private或者protect,為了保證測試功能代碼的唯一性,我選擇使用reflect方式,來確保測試工作的順利。我使用的reflection-utils來完成反射工作。

結束語

robolectric的基本認識如上,但是具體在測試中很多問題需要逐一解決,后續再一一整理出來。

希望大家多拍磚,特別是有錯誤的地方重拍都沒事~


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com