测试用例:Android Junit单元测试、异步测试简介及框架指南
晓晓 2018-02-27 来源 :网络 阅读 666 评论 0

摘要:Android Junit单元测试、异步测试简介及框架指南

本文解决的问题

  1. 如何使用junit 做Android 单元测试

  2. 如何使用junit 做Android 异步接口单元测试

  3. 使用作者封装的框架,优雅地用junit 做Android 异步接口单元测试 [doge]


  Junit 作为Android Studio 原生支持的测试框架可以很方便的执行单元测试,并且通过注解 @Test 可以直接标记方法为测试case 然后在子线程中执行。 标记为@UiThreadTest 时,测试case 将在 ui线程中执行。


  但是由于junit 本身的设计,当每个test方法执行结束时,该方法的运行线程会一并kill掉, 因此对于异步调用的方法,子线程会一并回收,回调函数也无法执行。


  举个例子,以下的测试case 将无法收到回调并会报错

  @Runwith(Junit4.calss)
  class Test1{
      public static final String TAG="sample test";
      @Test
      public void test1(){
          new YourAsyncJob().run(new YourAsyncTestCallback(){
              @Override
              public void onFinished(){
                  Log.i(TAG, "async call back");
              }
          });
          Log.i(TAG, "run async ok");
      }
  }

  解决方法 阻塞 test case


  既然测试线程死掉之后对应子任务都会失败,最直接的方案就是直接阻塞对应

 

 <span id="section1">1 线程锁</span>

  庆幸Java 提供了极其好用的原生api。CountDownLatch 能够直接阻塞线程,等待完成。 当调用 await()方法时,对应线程会阻塞至 countdownlatch 的 count 变为0 时,恢复运行。因此我们得到以下方案

  @Runwith(Junit4.calss)
  class Test1{
      public static final String TAG="sample test";
      @Test
      public void test1(){
          final CountDownLatch mutex = new CountDownLatch(1);
          new YourAsyncJob().run(new YourAsyncTestCallback(){
              @Override
              public void onFinished(){
                  Log.i(TAG, "async call back");
                  mutex.countDown();
              }
          });
          Log.i(TAG, "run async ok");
          mutex.await();
      }
  }

  跑了一下,似乎可行,log 出来了。

  然而在实际使用中又遇见了新的问题。


  2 Looper 阻塞(Handler thread)

  做过sdk的同学可能会遇见这样的需求:


  业务端的同学主线程(或handler线程)发起异步请求,执行完后(通过handler)回调至主线程(或handler线程)。

  在处理这个问题是,我们也发现 [方案一]中的回调函数事实上也只能在异步线程中执行,而不能切换回发起线程(测试线程)中执行。这显然不能满足我们优雅的异步接口的测试需求。于是我们需要新的方案2 Looper 阻塞 通过looper 阻塞 并且实现回调函数的线程切换。


  上述问题的根本就是handler 线程的回调及切换问题,这个时候由于测试线程是没有looper 的,我们需要为它营造一个这样的环境。 同时,既然有looper 的存在, 那么它的自旋功能也就可以满足我们对阻塞的需求,这样的情况下,我们似乎可以直接抛弃掉之前的CountDownLatch了。


  于是我们得到了以下代码

  @Runwith(Junit4.calss)
  class Test1{
      public static final String TAG="sample test";
      @Test
      public void test1(){
         Looper.prepare();
          //final CountDownLatch mutex = new CountDownLatch(1);
          new YourAsyncJob().run(new YourAsyncTestCallback(){
              @Override
              public void onFinished(){
                  Log.i(TAG, "async call back");
                  //mutex.countDown();
                  Looper.myLooper().quitSafely();
              }
          });
          Log.i(TAG, "run async ok");
          // mutex.await();
          Looper.loop();
      }
  }

  看起来是可以适应这样的过程,于是开始愉快的测试起来,但是很快,又遇见了新的问题。


  3 Handler thread + 封装

  自动化测试好处在于,自动的批量地执行测试case。于是在接下来的过程中我们用到了

      @RunWith(Parameterized.class)

  和@Parameterized.Parameters 注解来执行参数化的批量输入。


  于是新的问题出现了,由于实际运行时@Test方法运行在同一个子线程,因此多次Looper.prepare() 显然是不实际的,(会有RuntimeException)。


  于是最直接解决的办法是,一开始prepare好么?


  事实上也不行,这样的情况回存在如下问题。


  何时执行Looper.myLooper().quitSafely()

  熟悉Looper 的朋友知道,一旦quit之后,Looper 的queue 将无法使用。 而为了使阻塞的@Test线程恢复运行至结束,又必须在[方案2]的基础上解除loop().


  于是为了满足这样的情况,我们只能通过另起一个HandlerThread 执行这种需要跨线程回调的接口测试。然后在回调执行完毕前,阻塞最初的测试线程@Test线程,保证HandlerThread 的存活。(这里我们每次setup 都会新起一个线程,原因是,无法跨TestCase 重用这个线程,当Case执行完后,该线程会被系统强制回收)


  于是获得了如下的内容

 

 @RunWith(Parameterized.class)
  class Test1{
      public static final String TAG="sample test";
      private HandlerThread t;
      private Handler tH;
      @Parameterized.Parameters
      public static Collection<Object[]> data() {
          //测试数据
          return Arrays.asList(new Object[][]{
                  {"TES-1085-7", "TES-1085-7"},
                  {null, null},
          });
      }
      @Before
      public void setUp() throws Exception {
            t = new HandlerThread("test");
            t.start();
            tH = new Handler(t.getLooper());
      }
      @Test
      public void test1(){
          final CountDownLatch mutex = new CountDownLatch(1);
          tH.post(new Runnable(){
              @Override
              public void run(){
                  new YourAsyncJob().run(new YourAsyncTestCallback(){
                      @Override
                      public void onFinished(){
                          Log.i(TAG, "async call back");
                          mutex.countDown();
                      }
              }
          });
          Log.i(TAG, "run async ok");
          });
         mutex.await();
      }
  }

  4 优化及处理异常

  [方案3]基本能够处理一般的批量测试。但是作为一个严谨的程序员,这样的代码显然是不够优雅的。于是我们需要二次封装,封装后的代码调用会简洁很多,如下

 

 @RunWith(Parameterized.class)
  class Test1 extend ZCCBase{
      public static final String TAG="sample test";
      @Parameterized.Parameters
      public static Collection<Object[]> data() {
          //测试数据
          return Arrays.asList(new Object[][]{
                  {"TES-1085-7", "TES-1085-7"},
                  {null, null},
          });
      }
      @Before
      public void setUp() throws Exception {
           super.setUp();
      }
      @Test
      public void test1(){
          runAsyncTest(new AsyncTest(){
              @Override
              public void onRun(){
                  new YourAsyncJob().run(new YourAsyncTestCallback(){
                      @Override
                      public void onFinished(){
                          onAsyncTestFinished();
                      }
              }
          });
      }
  }

  是不是优雅了很多,具体框架和demo使用可以参考 我的github

  

    还没完,我们还剩下一个问题。实际操作时,异步线程中的Assert Error 如果直接抛出的话,并不能在Android Studio 的Run Text 窗口中直接显示出来,而是会显示成 进程crash 的日志,真实原因需要去logcat 中查找。这显然不是健全的,因此我们还需要把对应的Throwable 抛回测试线程。 这一功能也已经封装在 我的github中。


本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注职坐标软件测试之测试工具频道!


本文由 @晓晓 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程