一、介绍
自己百度去吧。
二、项目配置
1、针对Android Studio
在build.gradle中添加:
android {
testOptions {
unitTests {
includeAndroidResources = true
}
}
}
dependencies {
testImplementation 'junit:junit:4.12'
testImplementation 'org.robolectric:robolectric:3.6.1'
}
2、对mac用户
需要配置默认的JUnit测试运行器配置。否则会出现
java.io.FileNotFoundException: build\intermediates\bundles\debug\AndroidManifest.xml (系统找不到指定的路径。)
(1)编辑运行配置 Defaults → Android JUnit
(2)Working directory的值修改为$MODULE_DIR$
3、附加功能包
Robolectric为了减少外部依赖数量,将shadow包分割成多个功能包,可根据需要添加
附加包.png
使用例子:
testImplementation 'org.robolectric:shadows-support-v4:latest.release'
三、简单测试用例
1、Activity测试
(1)初始化
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class MyActivityTest {
MyActivity mMyctivity;
TextView mTextView;
Button mButton;
ActivityController<MyActivity> mActivityController;
@Before
public void init(){
mActivityController = Robolectric.buildActivity(MyActivity.class).create();
mMyctivity = mActivityController.get();
mTextView = mMyctivity.findViewById(R.id.textView);
mButton = mMyctivity.findViewById(R.id.login);
}
}
(2)生命周期测试
@Test
public void TestLifeCycle(){
assertEquals("onCreate",mTextView.getText());
mActivityController.start();
assertEquals("onStart",mTextView.getText());
mActivityController.resume();
assertEquals("onResume",mTextView.getText());
mActivityController.visible();
}
(3)UI测试
@Test
public void TestUI(){
Button mInverseBtn = mMyctivity.findViewById(R.id.inverseBtn);
assertTrue(mInverseBtn.isEnabled());
CheckBox mCheckBox = mMyctivity.findViewById(R.id.checkbox);
mCheckBox.setChecked(true);
mInverseBtn.performClick();
assertTrue(!mCheckBox.isChecked());
mInverseBtn.performClick();
assertTrue(mCheckBox.isChecked());
}
(4)Toast测试
@Test
public void TestToast(){
mMyctivity.findViewById(R.id.toastBtn).performClick();
assertEquals(ShadowToast.getTextOfLatestToast(),"test toast");
}
(5)Dialog测试
@Test
public void TestDialog(){
//与预测结果相反,待定
mActivityController.start().resume().visible();
Button mDialogBtn = mMyctivity.findViewById(R.id.dialogBtn);
assertTrue(mDialogBtn.isEnabled());
mDialogBtn.performClick();
AlertDialog lastAlertDialog = ShadowAlertDialog.getLatestAlertDialog();
assertTrue(lastAlertDialog == null);
}
(6)跳转测试
@Test
public void TestIntent(){
mButton.performClick();
Intent expectedIntent = new Intent(mMyctivity,MainActivity.class);
Intent actual = ShadowApplication.getInstance().getNextStartedActivity();
assertEquals(expectedIntent.getComponent(),actual.getComponent());
}
(7)资源测试
@Test
public void TestResource(){
Application application = RuntimeEnvironment.application;
String appName = application.getString(R.string.app_name);
assertEquals("RobolectricApp",appName);
}
2、Service测试
(1)Service代码
public class MyService extends IntentService{
public MyService(String name) {
super(name);
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
SharedPreferences.Editor editor = getApplicationContext().getSharedPreferences("SERVICE",MODE_PRIVATE).edit();
editor.putString("data","serviceData");
editor.apply();
}
}
(2)测试代码
@Test
public void TestService(){
Application application = RuntimeEnvironment.application;
MyService myService = Robolectric.setupIntentService(MyService.class);
myService.onHandleIntent(new Intent());
SharedPreferences preferences = application
.getSharedPreferences("SERVICE", Context.MODE_PRIVATE);
assertEquals(preferences.getString("data",""),"serviceData");
}
3、BroadcastReceiver测试
(1)BroadcastReceiver代码
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
SharedPreferences.Editor editor = context.getSharedPreferences("TEST", Context.MODE_PRIVATE).edit();
String data = intent.getStringExtra("data");
editor.putString("data", data);
editor.apply();
}
}
(2)测试代码
@Test
public void TestBroadcast(){
ShadowApplication shadowApplication = ShadowApplication.getInstance();
String action = "com.example.luzeping_sx.BRADCAST";
Intent intent = new Intent(action);
intent.putExtra("data","myData");
assertTrue(shadowApplication.hasReceiverForIntent(intent));
MyReceiver myReceiver = new MyReceiver();
myReceiver.onReceive(RuntimeEnvironment.application,intent);
SharedPreferences preferences = shadowApplication.getApplicationContext()
.getSharedPreferences("TEST", Context.MODE_PRIVATE);
assertEquals("myData",preferences.getString("data",""));
}
4、API测试
(1)初始化
private final String TAG = "ApiTest";
private Retrofit retrofit;
private RetrofitService retrofitService;
@Before
public void setUp(){
ShadowLog.stream = System.out;
retrofit = new Retrofit.Builder()
.baseUrl("https://api.douban.com/v2/")
.addConverterFactory(GsonConverterFactory.create())
.build();
retrofitService = retrofit.create(RetrofitService.class);
}
(2)api测试
@Test
public void TestApi(){
try {
Call<Book> call = retrofitService.getSearchBook("边城",null,0,1);
Response<Book> response = call.execute();
Gson gson = new Gson();
Log.d(TAG,gson.toJson(response));
assertNotNull(response);
assertNotNull(response.body());
}catch (IOException e){
e.printStackTrace();
}
}
四、注解
1、@RunWith
为测试类配置运行器。
2、@Config
为测类配置运行时配置。
如果想对对应包下的测试类进行相同配置,在 src/test/resources 下创建和对应包名相同的文件夹而后再该文件夹下添加 robolectric.properties 文件。
example:
# src/test/resources/com/mycompany/app/robolectric.properties
sdk=18
manifest=some/build/path/AndroidManifest.xml
shadows=my.package.ShadowFoo,my.package.ShadowBar
3、配置阿里云镜像仓库
Robolectric在每次运行的时候都需要更新它的依赖库。就算是科学上网的情况下下载速度依旧不够理想。因此推荐配置阿里云镜像仓库,提升下载速度。
步骤:
(1)自定义RobolectricRunner
public class MyRoboRunner extends RobolectricTestRunner{
/**
* Creates a runner to run {@code testClass}. Looks in your working directory for your AndroidManifest.xml file
* and res directory by default. Use the {@link Config} annotation to configure.
*
* @param testClass the test class to be run
* @throws InitializationError if junit says so
*/
public MyRoboRunner(Class<?> testClass) throws InitializationError {
super(testClass);
// 从源码知道MavenDependencyResolver默认以RoboSettings的repositoryUrl
// 和repositoryId为默认值,因此只需要对RoboSetting进行赋值即可
RoboSettings.setMavenRepositoryId("alimaven");
RoboSettings.setMavenRepositoryUrl("http://maven.aliyun.com/nexus/content/groups/public/");
}
}
(2)在测试类中配置该运行器
@RunWith(MyRoboRunner.class)
public class MyServiceTest {
五、Shadow
Robolectric定义了很多Shadow类,它们大多扩展或者修改了Android的实现类【例如ShadowActivity,ShadowApplication】。
测试时,当某个Android类被实例化时,Robolectric框架会优先去搜索它的Shadow类并创建一个Shadow对象来关联它。
当Android对象的某个方法被调用时,Robolectric会首先调用Shadow类的对应方法(如果有的话)。
Robolectric测试框架中,它包含了ShadowView、ShadowCanvas等影子类,即当测试运行起来时最终调用的是这些Shadow类。
ShadowView中覆盖了draw()方法,在draw方法中最终调用的是canvas.draw()
image.png
上面也有提到,Robolectric框架中包含ShadowCanvas类,而在该ShadowCanvas中它覆写了Canvas几乎所有的drawXXX()方法,且这些方法中并没有实际去调用Canvas的draw()方法。总而言之,Robolectric测试框架实际上并没有真正的绘制视图,我猜测这也是为什么它可以如此快速运行的原因。
六、踩的坑
讲道理,这框架的文档也太少了。
基本现在用法是白盒测试,对简单依赖的工程进行测试感觉可以很方便,但工程一旦复杂起来测试十分蛋疼。
1、你的路径可能找不到
解决方案:重写RobolectricTestRunner的getManifest()方法,将路径写死
public class MyRoboRunner extends RobolectricTestRunner{
@Override
protected AndroidManifest getAppManifest(Config config) {
//TODO 因为测试框架找不到工程的这几个文件 所以只能用绝对路径去指定它们。暂时还没有更好的解决方案。
//这些是我项目自己的路径,用的话记得改成自己的
String appRoot = "../";
String resDir = appRoot + "build/intermediates/res/merged/debug/";
String assetDirt = appRoot + "build/intermediates/assets/debug/";
return new AndroidManifest(Fs.fileFromPath("../AndroidManifest.xml"),
Fs.fileFromPath(resDir),Fs.fileFromPath(assetDirt)){
@Override
public List<ResourcePath> getIncludedResourcePaths() {
List<ResourcePath> paths = super.getIncludedResourcePaths();
return paths;
}
};
}
}
2、文档没有说明,框架里的类你根本不知道怎么用。
例如:Robo开头的一系列覆盖类。
3、Application过于复杂,你需要一个ShadowApplication去覆盖你原本的App类。这样就导致了你不仅要写测试用例,你还要编写测试用逻辑。这部分浪费的时间值不值得见仁见智。
4、三方库导入问题。
如果你的三方库是在gradle中配置,那配置时不需要任何配置。
但如果直接运行的话会出现 java.lang.VerifyError: Expecting a stackmap frame at branch target 的异常。
解决方案:
配置JVM参数
(1)打开Run 的 Edit Configuration
(2)Android Junit 中选择你的运行选项,切记要选中你的运行对象!
(3)VM options中添加:-noverify
七、总结
这个框架看起来好像很牛逼。什么不借助虚拟机就能跑界面逻辑,但其实用起来没那么方便,还一堆坑。官方文档还只给了最简单的用例说明。What the fuck...
偶尔整理整理,其实还是挺不错的。
哦差点忘了。附上demo地址:https://github.com/assdd215/RobolectricApp
网友评论