Robolectric 之 Jessyan Arm框架之Mvp

2019-08-09  本文已影响0人  DrChenZeng

Robolectric 实战解耦整个系列:
Robolectric 之 Jessyan Arm框架之Mvp 单元测试解耦过程(View 层)
Robolectric 之 Jessyan Arm框架之Mvp 单元测试解耦过程(Presenter 层)
Robolectric 之 Jessyan Arm框架之Mvp 单元测试本地json测试
github的链接: https://github.com/drchengit/Robolectric_arm_mvp

我的项目用的是jassyan的arm快速开发框架!
我做robolectric单元测试时,想哭!!
因为他的dagger2 用得飞起,我写代码的时候一边用一边说着:
"牛啤了!大兄弟!"
“哦!还能这么用,牛啤!牛啤”
但是在解耦单元测试的时候:
“这是啥哟!这个东西从哪里来的?”
“又报一个空指针”
“我是照着demo代码一步步敲下来的!报错是啥子情况?”
如果你用了arm 而且要做Robolectric 单元测试的话,不妨看看哟!如果不会Robolectric 建议先学学再看:测试资源放送

第一步: 写个登录功能

熟悉的配方新建mvp.png

LoginActvity

public class LoginActivity extends BaseActivity<LoginPresenter> implements LoginContract.View {
···
 @OnClick(R.id.tv_login)
    public void onViewClicked() {
        mPresenter.login();
    }
···
}

LoginPresenter

public class LoginPresenter extends BasePresenter<LoginContract.Model, LoginContract.View> {
···
public void login() {
        if(mRootView.getMobileStr().length() != 11){
            mRootView.showMessage("手机号码不正确");
            return;
        }
        if(mRootView.getPassWordStr().length() < 1){
            mRootView.showMessage("密码太短");
            return;
        }
        //调用登录接口,正确的密码:abc  手机号只要等于11位判断账号为正确
        mModel.login(mRootView.getMobileStr(),mRootView.getPassWordStr())
                .compose(RxUtils.applySchedulers(mRootView))
                .subscribe(new MyErrorHandleSubscriber<User>(mErrorHandler) {
                    //这个类是我自定义的一个类,统一拦截所有error 并回调给: ResponseErrorListenerImpl
                    // 可以不统一处理,直接重写覆盖:
//                     @Override
//                    public void onError(@NonNull Throwable t) {}
                    @Override
                    public void onNext(User user) {
                            mRootView.loginSuccess();
                    }
                });

    }
···
}

LoginModel

public class LoginModel extends BaseModel implements LoginContract.Model {
···
@Override
    public Observable<User> login(String mobileStr, String passWordStr) {
        //调用登录接口,正确的密码:abc  手机号只要等于11位判断账号为正确
        String name;
        if(passWordStr.equals("abc")){//正确密码,
            name = "drchengit";
        }else {
            name = "drchengi";
        }

        //由于不知道上哪里去找一个稳定且长期可用的登录接口,所以用的接口是github 上的查询接口:https://api.github.com/users/drchengit
        // 这里的处理是正确的密码,请求存在的用户名:drchengit  错误的密码请求不存在的用户名: drchengi
        // 将就一下
        return mRepositoryManager.obtainRetrofitService(CommonService.class).getUser(name);
    }
···
}

注意我通过Okhttp 的插值器 回调GlobalHttpHandlerImpl 类的onHttpResultResponse()方法,如果返回 "no found" 内部会 throw 一个 "密码错误" 的自定义异常,被框架捕获并打印 Toast

 
public class GlobalHttpHandlerImpl implements GlobalHttpHandler {
    
    @Override
    public Response onHttpResultResponse(String httpResult, Interceptor.Chain chain, Response response) {
        if (!TextUtils.isEmpty(httpResult) && RequestInterceptor.isJson(response.body().contentType())) {
            User user;
            //                https://blog.csdn.net/qfikh/article/details/75669939
//                List<User> list = ArmsUtils.obtainAppComponentFromContext(context).gson().fromJson(httpResult, new TypeToken<List<User>>() {
//                }.getType());
            user = ArmsUtils.obtainAppComponentFromContext(context).gson().fromJson(httpResult, User.class);
            if(user.isLoginFaild()){
                throw new MyNetException(10001,"密码错误");
            }
        }


 
        return response;
    }

 ···
}

我省略了过程,总之就是输入正确的手机号和密码就可以登录,输错就会提示"密码错误"。

第二步导包和配置

其实androidx 已经出了,https://github.com/robolectric/robolectric,但是jessyan在简书回复我androidx 现在没打算适配(第一次收到作者的回复,可把我牛逼坏了,学android 的人都这么平易近人吗?)我也还有没有处理迁移的bug,所以用了这框架只有将就sdk 27版本的测试用一下。

android {

  //单元测试
    testOptions {
        unitTests {
            includeAndroidResources = true
        }
    }
}

dependencies {
 ·····

 //单元测试
    testImplementation 'org.robolectric:robolectric:3.8'
    testImplementation "org.robolectric:shadows-support-v4:3.4-rc2"
    //依赖隔离
    testImplementation "org.mockito:mockito-core:2.11.0"

  
    }
} 

注意: includeAndroidResources = true这要加上

第三步测试View 层

图片.png
package me.jessyan.mvparms.demo.mvp.ui.activity.login;


import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowLog;
import org.robolectric.shadows.ShadowToast;

import static org.junit.Assert.*;

@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 27)
public class LoginActivityTest {
    TextView loginTv;

    EditText phoneEt;
    EditText passWrodEt;
    private LoginActivity loginActivity;


    @Before
    public void setUp() {
        ShadowLog.stream = System.out;
        loginActivity = Robolectric.buildActivity(LoginActivity.class)
                .create()
                .resume()
                .get();
        loginTv = loginActivity.findViewById(R.id.tv_login);
        phoneEt = loginActivity.findViewById(R.id.et_mobile);
        passWrodEt = loginActivity.findViewById(R.id.et_pass);

    }


    @Test
    public  void login(){
        //直接点击登录
        loginTv.performClick();
        Assert.assertEquals("手机号码不正确", ShadowToast.getTextOfLatestToast());


    }

}
public class AppLifecyclesImpl implements AppLifecycles {

  ···
    @Override
    public void onCreate(@NonNull Application application) {
      try {
            if (LeakCanary.isInAnalyzerProcess(application)) {
                // This process is dedicated to LeakCanary for heap analysis.
                // You should not init your app in this process.
                return;
            }
        }catch (NullPointerException e){

        }
      ···
    }

  ···
}

LeakCanary 是内存泄露,对单元测试没啥用,直接try {}catch

    @Test
    public  void login(){
        //直接点击登录
//        loginTv.performClick();
//        Assert.assertEquals("手机号码不正确", ShadowToast.getTextOfLatestToast());
        phoneEt.setText("13547250999");
        //没有输入密码
//        loginTv.performClick();
//        Assert.assertEquals("密码太短", ShadowToast.getTextOfLatestToast());
        //错误密码
        passWrodEt.setText("aaaa");
        loginTv.performClick();
        //这里是验证网络框架提示
        Assert.assertEquals("密码错误", ShadowToast.getTextOfLatestToast());

    }
        private void initRxJava() {
        RxJavaPlugins.reset();
        RxJavaPlugins.setIoSchedulerHandler(new Function<Scheduler, Scheduler>() {
            @Override
            public Scheduler apply(Scheduler scheduler) throws Exception {
                return Schedulers.trampoline();
            }
        });
        RxAndroidPlugins.reset();
        RxAndroidPlugins.setMainThreadSchedulerHandler(new Function<Scheduler, Scheduler>() {
            @Override
            public Scheduler apply(Scheduler scheduler) throws Exception {
                return Schedulers.trampoline();
            }
        });

    }
@Singleton
public class RepositoryManager implements IRepositoryManager {
 ···
    private <T> T createWrapperService(Class<T> serviceClass) {
        // 通过二次代理,对 Retrofit 代理方法的调用包进新的 Observable 里在 io 线程执行。
        return (T) Proxy.newProxyInstance(serviceClass.getClassLoader(),
                new Class<?>[]{serviceClass}, new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, @Nullable Object[] args)
                            throws Throwable {
                        if (method.getReturnType() == Observable.class) {
                            // 如果方法返回值是 Observable 的话,则包一层再返回
                            return Observable.defer(() -> {
                                final T service = getRetrofitService(serviceClass);
                                // 执行真正的 Retrofit 动态代理的方法
图片.png
                        }
                        // 返回值不是 Observable 的话不处理
                        final T service = getRetrofitService(serviceClass);
                        return getRetrofitMethod(service, method).invoke(service, args);
                    }
                });
    }
···
}

        private void initRxJava() {
        RxJavaPlugins.reset();
        RxJavaPlugins.setIoSchedulerHandler(new Function<Scheduler, Scheduler>() {
            @Override
            public Scheduler apply(Scheduler scheduler) throws Exception {
                return Schedulers.trampoline();
            }
        });

        //这个哟
        RxJavaPlugins.setSingleSchedulerHandler(new Function<Scheduler, Scheduler>() {
            @Override
            public Scheduler apply(Scheduler scheduler) throws Exception {
                return Schedulers.trampoline();
            }
        });

        RxAndroidPlugins.reset();
        RxAndroidPlugins.setMainThreadSchedulerHandler(new Function<Scheduler, Scheduler>() {
            @Override
            public Scheduler apply(Scheduler scheduler) throws Exception {
                return Schedulers.trampoline();
            }
        });


    }
 @Test
    public  void login(){
        initRxJava();
        //直接点击登录
        loginTv.performClick();
        Assert.assertEquals("手机号码不正确", ShadowToast.getTextOfLatestToast());
        phoneEt.setText("13547250999");
        //没有输入密码
        loginTv.performClick();
        Assert.assertEquals("密码太短", ShadowToast.getTextOfLatestToast());
        //错误密码
        passWrodEt.setText("aaaa");
        loginTv.performClick();
        //这里是验证网络框架提示
        Assert.assertEquals("密码错误", ShadowToast.getTextOfLatestToast());
        //正确密码登录
        passWrodEt.setText("abc");
        loginTv.performClick();
        Assert.assertEquals("登录成功",ShadowToast.getTextOfLatestToast());

        //验证跳转
        ShadowActivity shadowActivity = Shadows.shadowOf(loginActivity);
        Intent intent = shadowActivity.getNextStartedActivity();
        Assert.assertEquals(intent.getComponent().getClassName(), MainActivity.class);


    }

Robolectric 实战解耦整个系列:
Robolectric 之 Jessyan Arm框架之Mvp 单元测试解耦过程(View 层)
Robolectric 之 Jessyan Arm框架之Mvp 单元测试解耦过程(Presenter 层)
Robolectric 之 Jessyan Arm框架之Mvp 单元测试本地json测试
github的链接: https://github.com/drchengit/Robolectric_arm_mvp
测试资源放送

基本的配置 | https://www.jianshu.com/p/7a4024925193
常见的坑(分包导致测试报错等) | https://blog.csdn.net/weixin_34204057/article/details/91418305

我是drchen,一个温润的男子,版权所有,未经允许不得抄袭。

上一篇下一篇

猜你喜欢

热点阅读