最近抽时间检查了一下年前有关单元测试的八篇博客,查漏补缺了一下。后面如果有更多关于单元测试的心得收获,也会继续补充。
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版。希望大家多多点赞支持!