Android单元测试实践

发表于:2018-1-26 14:24

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

 作者:smallsoho    来源:个人博客

  为什么要引入单元测试
  一般来说我们都不会写单元测试,为什么呢?因为要写多余的代码,而且还要进行一些学习,入门有些门槛,所以一般在工程中都不会写单元测试。那么为什么我决定要写单元测试。因为两个条件
  1.我很懒:我每次改完都很懒测试
  2.我很怂:我要是不测试,没有一次通过的信心,于是我还是要测试。。。
  这篇博客看完并不会让你完全掌握单元测试,但是会给你在单元测试的开始有一个好的指引
  大大提高工作效率
  单元的概念比较模糊,可以是一个方法,可以是一个时机,但是不是一整套环节,一整套环节那就是集成测试了。为什么说大大提高了工作效率。有以下几个场景
  1.一个计算数字的方法你发现你写错了,你把+写成了*,处于责任心,测试一下。首先你点击build,然后登上几分钟,期间可以喝个茶,看个朋友圈。。。。。然后,你发现你写成了/,再来一次吧少年。。。。
  2.你要修改一个项目,这时候你改了一个函数的小细节,线上crash了。。。
  3.你在原来的基础上改了一下业务,有一个一万人只有一个人用的场景你不知道,UI出Bug
  等等等等,这些场景在普通的测试中无法很好的发现,毕竟你不能让QA每次都把之前的业务都测试一下。所以这种场景如何解决?
  写单元测试
  你可以测试一个方法而不用再跑一次程序,项目写大了build一次可是很耗时的。写好的单元测试也不用删除,ci会帮你每次运行一下,来保证之前的逻辑是没有问题的。
  单元测试框架的选择
  Android的单元测试框架林林总总,目测10多种,所以,挑选是个问题,我总认为google官方推荐的和大众都选择使用的较好。所以我推荐(放心用,绝对是主流套装)
      //local test
      testCompile 'junit:junit:4.12'
      testCompile 'org.mockito:mockito-core:2.7.22'
      testCompile 'org.robolectric:robolectric:3.3.2'
      //android test
      androidTestCompile 'org.mockito:mockito-core:2.7.22'
      androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
          exclude group: 'com.android.support', module: 'support-annotations'
      })
  1.JUnit4:基础的单元测试使用
  2.espresso:UI测试使用
  3.Mockito:Mock你要测试的类,
  4.Robolectric(可选):让你在JVM环境中模拟Android的环境,这里稍后解释为什么可选
  解释一下依赖,首先测试分成两种
  1.本地单元测试: 位于 module-name/src/test/java/
  不需要Android环境的测试用例写在这里
  1.Android Instrumentation测试: 位于 module-name/src/androidTest/java/。
  需要使用Android环境的测试用例写在这里,例如你要使用TextUtils.isEmpty(“”),这个函数是android包中的,需要写在这个包下,写在上面运行时会报错。
  引用自 Android官方文档
  依次解释
  ●JUnit4是写测试用例的,所有的测试用例都是用JUnit4来写,所以这是基础库,不解释。
  ●espresso是Google官方的UI测试库,可以对UI做白盒测试
  ●Mockito是用来做Mock的,并且Mockito2.6+已经支持Android Instrumentation下使用什么是Mock?
  ●Robolectric是可以在JVM上模拟Android环境,也就是你可以在本地单元测试中使用这个库模拟Android环境来做到调用Android Api的测试,为什么要用呢?因为可能你在CI中进行测试你无法让系统开启一个Android模拟器或者真机来进行仪器测试,这也就是为什么这个库可选,你如果不用这个场景,就是不启动虚拟机才测试,你也就不用引入这个库。而且打包使用真机过程比较漫长,使用这个直接在JVM上跑速度更快
  常见的单元测试
  本文不能把所有的情况都介绍到,也不能将所有的框架都介绍完全,挑选几个主要的例子介绍来让大家理解总体上的结构,然后自己挑选侧重点根据文档深入的学习。
  本地单元测试
  Example:验证一个数组的长度是否等于5,并且是否调用了add(1)这个操作,使用local test完成
  public class ExampleUnitTest {
      @Test
      public void addition_isCorrect() throws Exception {
          List<Integer> mockList = Mockito.mock(List.class);
          mockList.add(1);
          Mockito.when(mockList.size()).thenReturn(5);
          Mockito.verify(mockList).add(1);
          assertEquals(5,mockList.size());
      }
  }
  首先我们知道一个问题,mockito所mock出来的对象都是假的,是的,没错,你用了一个假数组,如果想要用真的,老老实实new一个,也就是说针对一个数组,所有的操作都是拿不到正常结果的,除非你想我上面那种方式去指定他。你如果执行mockList.size(),返回的是0,最后一句的作用是判断两个值是否相等。Mockito.verify是验证函数,可以验证这个方法是否被执行过。
  Example:这次不用假数组了,用真的!然后我们偷梁换柱,就是部分mock
  public class Example2UnitTest {
      @Test
      public void spyText() {
          List<Integer> mockList = new ArrayList<>();
          mockList = Mockito.spy(mockList); //如果直接spy
          mockList.add(1);
          mockList.add(2);
          Mockito.when(mockList.size()).thenReturn(5);
          assertEquals(5, mockList.size());
          assertEquals(mockList.get(0) == 1, true);
          Mockito.when(mockList.size()).thenCallRealMethod();
          assertEquals(2, mockList.size());
      }
  }
  首先区分一下spy一个对象和spy一个类的区别:如果我此处调用Mockito.spy(List.class),那么你得到的list仍然是假的,原因是看源码。
      @Incubating
      public static <T> T spy(Class<T> classToSpy) {
         return MOCKITO_CORE.mock(classToSpy, withSettings()
                 .useConstructor()
                 .defaultAnswer(CALLS_REAL_METHODS));
      }
  很清晰,spy一个类是创建一个类的mock对象,然后调用一个mock类的真实返回,mock得到的类就是假的类,所以她的真实的返回值也是假的。所以我们此处spy一个对象。
  spy一个对象是默认的对象的返回都使用真实数据返回,如果你进行修改,那么就用你修改的类来做返回。这样就能做到部分修改。我们还调用了这个Mockito.when(mockList.size()).thenCallRealMethod();这句的意思很清晰,就是使用这个函数的真实返回值,就算是一个mock出来的“假对象”,仍然可以用这个的到真实的返回值。
  Android Instrumentation测试
  Android Instrumentation测试跟本地测试区别不大,唯一的区别是运行的时候需要我们选择一个设备,然后gradle会打包测试用的Apk在手机上运行测试用例,如果想打log,换成Log.d等方法在Android Monitor中查看
  1.Example: 查看当前的context是不是我的包名
  @RunWith(AndroidJUnit4.class)
  public class ExampleInstrumentedTest {
      @Test
      public void useAppContext() throws Exception {
          // Context of the app under test.
          Context appContext = InstrumentationRegistry.getTargetContext();
          assertEquals("com.ss.android.ies.mobcase.test", appContext.getPackageName());
      }
  }
  这是一个官方的例子,在androidTestCompile中,我们可以使用Api来发现一个当前的appContext,这在jvm中是做不到的。因为你没有android环境。这里判断一下是否一样。
  2.Example: 使用espresso在UI上进行测试
  androidTextCompile的优点是可以在android环境上进行测试,所以少不了UI的白盒测试,这里举一个例子,测试点击button之后editText的文字是否是123456
  @RunWith(AndroidJUnit4.class)
  public class TestActivityTest {
      @Rule
      public ActivityTestRule<TestActivity> activityTestRule = new ActivityTestRule<>(TestActivity.class);
      @Test
      public void buttonTest() {
          Espresso.onView(withId(R.id.button)).perform(click());
          Espresso.onView(withId(R.id.edit))
                  .check(matches(withText("123456")));
      }
  }
  此处很好理解,espresso的语法跟自然语言一样,写单元测试分为三个步骤
  ●找
  ●执行
  ●检查
  很简单的几句语法就能对UI进行测试,这里我们进行了对button进行定位,然后执行click操作。第二部进行检查,因为我的逻辑是点击按钮editText.setText(“123456”),所以这个单元测试是通过的。
  Thanks!

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

关注51Testing

联系我们

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

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

沪ICP备05003035号

沪公网安备 31010102002173号