Android单元测试(五):网络接口测试

来源:互联网 发布:网络名称大全5个字 编辑:程序博客网 时间:2024/06/10 03:06

温馨提示:如果你不太熟悉单元测试,可以先看下之前四篇基础框架使用。便于你更好的理解下面的内容。

在平日的开发中,我们用后台写好给我们接口去获取数据。虽然我们有一些请求接口的工具,可以快速的拿到返回数据。但是在一些异常情况的处理上就不太方便了。我列出以下几个痛点:

  • 快速的查看返回数据与数据的处理。(一般我们都是将写好的代码跑到手机上,点击到对应页面的对应按钮)

  • 异常信息的返回与处理。比如一个接口返回一个列表数据,如果列表为空呢?(找后台给我们模拟数据)网速不好呢?(不知道怎么搞…)网络异常呢?(关闭网络)

  • 后台有部分接口没有写好,你就只能等他了。

不知道上面的这三点有没有戳到你的痛处。如果扎心了,那么老铁你就有必要掌握今天的内容。

1.请求接口

我们就使用网络请求三件套(retrofit + okhttp + rxjava2)来举例。

首先添加一下依赖,同时记得加网络权限。

    //RxJava    compile 'io.reactivex.rxjava2:rxjava:2.1.7'    //RxAndroid    compile 'io.reactivex.rxjava2:rxandroid:2.0.1'    //okhttp    compile "com.squareup.okhttp3:okhttp:3.9.1"    //Retrofit    compile ("com.squareup.retrofit2:retrofit:2.3.0"){        exclude module: 'okhttp'    }    compile ("com.squareup.retrofit2:adapter-rxjava2:2.3.0"){        exclude module: 'rxjava'    }    compile "com.squareup.retrofit2:converter-gson:2.3.0"

测试接口:

public interface GithubApi {    String BASE_URL = "https://api.github.com/";    @GET("users/{username}")    Observable<User> getUser(@Path("username") String username);}

Retrofit的初始化,我们使用LoggingInterceptor来打印返回数据。

public class GithubService {    private static Retrofit retrofit = new Retrofit.Builder()            .baseUrl(GithubApi.BASE_URL)            .client(getOkHttpClient())            .addConverterFactory(GsonConverterFactory.create())            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())            .build();    public static GithubApi createGithubService() {        return retrofit.create(GithubApi.class);    }    private static OkHttpClient getOkHttpClient(){        return new OkHttpClient.Builder()                .addInterceptor(new LoggingInterceptor())                .build();    }}

测试代码:

@RunWith(RobolectricTestRunner.class)@Config(constants = BuildConfig.class, sdk = 23)public class ResponseTest {    @Before    public void setUp() {        ShadowLog.stream = System.out;        initRxJava2();    }    private void initRxJava2() {        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();            }        });    }    @Test    public void getUserTest() {        GithubService.createGithubService()                .getUser("simplezhli")                .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() {}                });    }}

上面的代码中,因为网络请求是异步的,所以我们直接测试是不能直接拿到数据,因此无法打印出Log以及测试。所以我们使用initRxJava2()方法将异步转化为同步。这样我们就可以看到返回信息。测试结果如下:

这里写图片描述

上面的例子为了简单直观的说明,所以将请求接口的方法写到了测试类中,实际中我们可以Mock方法所在的类直接调用请求方法。配合Robolectric对View控件的状态进行测试。

如果你觉得每次测试都要加initRxJava2这段方法很麻烦,你可以抽象出来,或者使用@Rule

public class RxJavaRule implements TestRule {    @Override    public Statement apply(final Statement base, Description description) {        return new Statement() {            @Override            public void evaluate() throws Throwable {                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();                    }                });                base.evaluate();            }        };    }}

2.模拟数据

1.使用拦截器模拟数据

利用okhttp的拦截器模拟响应数据。

public class MockInterceptor implements Interceptor {    private final String responseString; //你要模拟返回的数据    public MockInterceptor(String responseString) {        this.responseString = responseString;    }    @Override    public Response intercept(Interceptor.Chain chain) throws IOException {        Response response = new Response.Builder()                .code(200)                .message(responseString)                .request(chain.request())                .protocol(Protocol.HTTP_1_0)                .body(ResponseBody.create(MediaType.parse("application/json"), responseString.getBytes()))                .addHeader("content-type", "application/json")                .build();        return response;    }}

测试代码:

@RunWith(RobolectricTestRunner.class)@Config(constants = BuildConfig.class, sdk = 23)public class MockGithubServiceTest {    private GithubApi mockGithubService;    @Rule    public RxJavaRule rule = new RxJavaRule();    @Before    public void setUp() throws URISyntaxException {        ShadowLog.stream = System.out;        //定义Http Client,并添加拦截器        OkHttpClient okHttpClient = new OkHttpClient.Builder()                .addInterceptor(new LoggingInterceptor())                .addInterceptor(new MockInterceptor("json数据"))//<-- 添加拦截器                .build();        //设置Http Client        Retrofit retrofit = new Retrofit.Builder()                .baseUrl(GithubApi.BASE_URL)                .client(okHttpClient)                .addConverterFactory(GsonConverterFactory.create())                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())                .build();        mockGithubService = retrofit.create(GithubApi.class);    }    @Test    public void getUserTest() throws Exception {        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() {}                });    }}

虽然我们传入了错误的用户名,但是我们模拟的响应信息已经提前设定好了,所以测试结果不变。

利用这个思路,我们可以修改MockInterceptor的code,模拟404的情况。

这里写图片描述

2.MockWebServer

MockWebServer是square出品的跟随okhttp一起发布,用来Mock服务器行为的库。MockWebServer能帮我们做的事情:

  • 可以设置http response的header、body、status code等。
  • 可以记录接收到的请求,获取请求的body、header、method、path、HTTP version。
  • 可以模拟网速慢的网络环境。
  • 提供Dispatcher,让mockWebServer可以根据不同的请求进行不同的反馈。

添加依赖:

testCompile 'com.squareup.okhttp3:mockwebserver:3.9.1'

测试代码:

@RunWith(RobolectricTestRunner.class)@Config(constants = BuildConfig.class, sdk = 23)public class MockWebServerTest {    private GithubApi mockGithubService;    private MockWebServer server;    @Rule    public RxJavaRule rule = new RxJavaRule();    @Before    public void setUp(){        ShadowLog.stream = System.out;        // 创建一个 MockWebServer        server = new MockWebServer();        //设置响应,默认返回http code是 200        MockResponse mockResponse = new MockResponse()                .addHeader("Content-Type", "application/json;charset=utf-8")                .addHeader("Cache-Control", "no-cache")                .setBody("{\"id\": 12456431, " +                         " \"name\": \"唯鹿\"," +                         " \"blog\": \"http://blog.csdn.net/qq_17766199\"}");        MockResponse mockResponse1 = new MockResponse()                .addHeader("Content-Type", "application/json;charset=utf-8")                .setResponseCode(404)                .throttleBody(5, 1, TimeUnit.SECONDS) //一秒传递5个字节,模拟网速慢的情况                .setBody("{\"error\": \"网络异常\"}");        server.enqueue(mockResponse); //成功响应        server.enqueue(mockResponse1);//失败响应        OkHttpClient okHttpClient = new OkHttpClient.Builder()                .addInterceptor(new LoggingInterceptor())                .build();        Retrofit retrofit = new Retrofit.Builder()                .baseUrl("http://" + server.getHostName() + ":" + server.getPort() + "/") //设置对应的Host与端口号                .client(okHttpClient)                .addConverterFactory(GsonConverterFactory.create())                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())                .build();        mockGithubService = retrofit.create(GithubApi.class);    }    @Test    public void getUserTest() throws Exception {        //请求不变        mockGithubService.getUser("simplezhli")                .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() {                    }                });        //验证我们的请求客户端是否按预期生成了请求        RecordedRequest request = server.takeRequest();        assertEquals("GET /users/simplezhli HTTP/1.1", request.getRequestLine());        assertEquals("okhttp/3.9.1", request.getHeader("User-Agent"));        // 关闭服务        server.shutdown();    }}

代码中的注释写的很清楚了,就不用过多的解释了,使用起来非常的简单。

执行结果(成功):

这里写图片描述

失败结果:

这里写图片描述

默认情况下 MockWebServer 预置的响应是先进先出的。这样可能对你的测试有限制,这时可以通过Dispatcher来处理,比如通过请求的路径来选择转发。

        Dispatcher dispatcher = new Dispatcher() {            @Override            public MockResponse dispatch(RecordedRequest request) throws InterruptedException {                if (request.getPath().equals("/users/simplezhli")){                    return new MockResponse()                            .addHeader("Content-Type", "application/json;charset=utf-8")                            .addHeader("Cache-Control", "no-cache")                            .setBody("{\"id\": 12456431, " +                                    " \"name\": \"唯鹿\"," +                                    " \"blog\": \"http://blog.csdn.net/qq_17766199\"}");                } else {                    return new MockResponse()                            .addHeader("Content-Type", "application/json;charset=utf-8")                            .setResponseCode(404)                            .throttleBody(5, 1, TimeUnit.SECONDS) //一秒传递5个字节                            .setBody("{\"error\": \"网络异常\"}");                }            }        };        server.setDispatcher(dispatcher); //设置Dispatcher

通过上面的例子,是不是可以很好的解决你的痛点,希望对你有帮助。只要我们有和后台有开发文档,约定好数据格式与字段名,那么我们可以更敏捷的去做我们的开发。本篇所有代码已上传至Github。希望大家多多点赞支持!

3.参考

  • Android单元测试框架Robolectric3.0介绍(二)

  • MockWebServer使用指南