Android单元测试:网络接口测试

发表于:2018-4-09 11:27

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:唯鹿    来源:CSDN

  在平日的开发中,我们用后台写好给我们接口去获取数据。虽然我们有一些请求接口的工具,可以快速的拿到返回数据。但是在一些异常情况的处理上就不太方便了。我列出以下几个痛点:
  快速的查看返回数据与数据的处理。(一般我们都是将写好的代码跑到手机上,点击到对应页面的对应按钮)
  异常信息的返回与处理。比如一个接口返回一个列表数据,如果列表为空呢?(找后台给我们模拟数据)网速不好呢?(不知道怎么搞…)网络异常呢?(关闭网络)
  后台有部分接口没有写好,你就只能等他了。
  不知道上面的这三点有没有戳到你的痛处。如果扎心了,那么老铁你就有必要掌握今天的内容。
  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。希望大家多多点赞支持!





上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理。
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号