Android單元測試(九):查漏補缺篇
最近抽時間檢查了一下年前有關單元測試的八篇部落格,查漏補缺了一下。後面如果有更多關於單元測試的心得收穫,也會繼續補充。
1.AssertJ
在Android單元測試(一):JUnit框架的使用中,我們介紹瞭如何使用JUnit來進行斷言。不多說實話JUnit使用起來還是不太友好,不是很直觀。所以補充介紹一下AssertJ。
AseertJ: JAVA 流式斷言器,什麼是流式,常見的斷言器一條斷言語句只能對實際值斷言一個校驗點,而流式斷言器,支援一條斷言語句對實際值同時斷言多個校驗點。
//AssertJ 2.x 對應 Java 7 以上.
testImplementation 'org.assertj:assertj-core:2.9.1'
//AssertJ 3.x 對應 Java 8 以上.
testImplementation 'org.assertj:assertj-core:3.10.0'
類中匯入:
// 靜態匯入所有方法
import static org.assertj.core.api.Assertions.*;
//android使用這個靜態匯入
import static org.assertj.core.api.Java6Assertions.*;
部分使用方法:
@Test
public void test(){
// 斷言空字串
assertThat("" ).isEmpty();
// as指定錯誤資訊
assertThat("weilu").as("沒有a或者b").contains("a").contains("b");
// 斷言相等
assertThat(42).isEqualTo(42);
// 斷言大於 大於等於
assertThat(42).isGreaterThan(38).isGreaterThanOrEqualTo(38);
// 斷言時間再指定範圍內 不在指定範圍內
assertThat(parse("2018-05-15" ))
.isBetween("2018-04-01", "2018-06-01")
.isNotBetween("2019-01-01", "2019-12-31");
// 斷言 列表是空的
assertThat(newArrayList()).isEmpty();
// 斷言 列表的開始 結束元素
assertThat(newArrayList(1, 2, 3)).startsWith(1).endsWith(3);
Map<String, Integer> foo = Maps.newHashMap("A", 1);
foo.put("B", 2);
foo.put("C", 3);
// 斷言 map 不為空 size
assertThat(foo).isNotEmpty().hasSize(3);
// 斷言 map 包含元素
assertThat(foo).contains(entry("A", 1), entry("B", 2));
}
當然對於Android還有 AssertJ-Android,它是AssertJ的拓展,更加便於我們斷言Android View。
常用的有以下五個,當然官方還提供了更多。
androidTestCompile 'com.squareup.assertj:assertj-android:1.2.0'
androidTestCompile 'com.squareup.assertj:assertj-android-support-v4:1.2.0'
androidTestCompile 'com.squareup.assertj:assertj-android-appcompat-v7:1.2.0'
androidTestCompile 'com.squareup.assertj:assertj-android-design:1.2.0'
androidTestCompile 'com.squareup.assertj:assertj-android-recyclerview-v7:1.2.0'
靜態匯入:
//android
import static org.assertj.android.api.Assertions.assertThat;
//support-v4
import static org.assertj.android.support.v4.api.Assertions.assertThat;
//appcompat-v7
import static org.assertj.android.appcompat.v7.api.Assertions.assertThat;
//design
import static org.assertj.android.design.api.Assertions.assertThat;
//recyclerview-v7
import static org.assertj.android.recyclerview.v7.api.Assertions.assertThat;
部分使用方法:
@Test
public void testView() {
// Button是否可見
assertThat(mJumpBtn).isVisible();
// LinearLayout 方向,子View數量
assertThat(mRoot)
.isVertical()
.hasChildCount(4);
// CheckBox是否未選中
assertThat(checkBox).isNotChecked();
}
檢視isNotChecked
原始碼可以的看到,就是利用AssertJ
做了一層封裝。
2.Robolectric相關
在Android單元測試(四):Robolectric框架的使用中,我說到了在gradle版本為3.0以上使用Robolectric會報錯Resources$NotFoundException,如果你真的需要在3.0以上使用(本人現使用3.1.2,親測有效),可以這樣:
在gradle中新增(推薦):
testOptions {
unitTests {
includeAndroidResources = true
}
}
或者在gradle.properties
中新增:
android.enableAapt2=false
最近將Robolectric版本從3.5.1升到3.8版本,出現錯誤:
java.lang.VerifyError: class org.robolectric.android.fakes.RoboMonitoringInstrumentation
overrides final method specifyDexMakerCacheProperty.()V
testImplementation "com.android.support.test:monitor:1.0.2"
3.網路介面測試相關
在Android單元測試(五):網路介面測試中我們有說到使用MockWebServer來模擬網路請求並返回資料,使用起來還是有些繁瑣。下面我推薦一個簡單方法:使用框架RESTMock
直接上程式碼,測試正常情況(可以設定響應時間):
@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 23)
public class RESTMockTest {
private GithubApi mockGithubService;
@Rule
public RxJavaRule rule = new RxJavaRule();
@Before
public void setUp(){
ShadowLog.stream = System.out;
// 啟動服務
RESTMockServerStarter.startSync(new JVMFileParser());
//定義Http Client,並新增攔截器
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new LoggingInterceptor())
.build();
//設定Http Client
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(RESTMockServer.getUrl()) //<--注意這裡
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
mockGithubService = retrofit.create(GithubApi.class);
}
@Test
public void getUserTest() throws Exception {
RESTMockServer.whenGET(pathContains("users"))
// .delay(TimeUnit.SECONDS, 5) // 模擬響應時長
.thenReturnFile(200, "json/users.json");
mockGithubService.getUser("weilu")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<User>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(User user) {
assertEquals("唯鹿", user.name);
assertEquals("http://blog.csdn.net/qq_17766199", user.blog);
}
@Override
public void onError(Throwable e) {
Log.e("Test", e.toString());
}
@Override
public void onComplete() {
}
});
}
}
測試異常情況:
@Test
public void testNotFound() throws Exception {
RESTMockServer.whenGET(pathEndsWith("weilu")).thenReturnString(404, "{message : \"伺服器異常\"}");
mockGithubService.getUser("weilu")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<User>() {
@Override
public void onSubscribe(Disposable d) {}
@Override
public void onNext(User user) {
assertEquals("唯鹿", user.name);
assertEquals("http://blog.csdn.net/qq_17766199", user.blog);
}
@Override
public void onError(Throwable e) {
Log.e("Test", e.toString());
}
@Override
public void onComplete() {}
});
}
4.資料庫的單元測試
資料庫這部分的單元測試我之前沒有提到,畢竟還是需要多思考多練習舉一反三,不可能將方方面面都說到。其實關於它的測試與之前沒有什麼區別。比如我們常使用的greenDAO
,DBFlow
,Realm
,在它的Github首頁就有單元測試的用例,不信你們去看看(手動滑稽)
5.參考
基本我們用到的熱門框架與專案(例如RxJava/谷歌官方應用架構等),都會有相關的單元測試,我們可以參考他們的測試用例來學習。當然所使用的測試框架基本都是我們之前提到的。不得不說國外還是很重視這方面的。
所有程式碼已上傳至Github。並補充了Kotlin版。希望大家多多點贊支援!