• Android单元测试实践

    发布:51Code 时间: 2018-04-02 13:50

  • 为什么要引入单元测试 一般来说我们都不会写单元测试,为什么呢?因为要写多余的代码,而且还要进行一些学习,入门有些门槛,所以一般在工程中都不会写单元测试。那么为什么我...

  • 为什么要引入单元测试

    一般来说我们都不会写单元测试,为什么呢?因为要写多余的代码,而且还要进行一些学习,入门有些门槛,所以一般在工程中都不会写单元测试。那么为什么我决定要写单元测试。因为两个条件

    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!
     

    原文作者:smallsoho
    上文内容来自:个人博客
    博为峰对此不表示赞同或者反对,也不为其提供证明,仅供阅读者交流参考。
    上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编 caoxiaoyan@51testing.com,我们将立即处理。
  • 上一篇:iOS单元测试常用的宏和测试方法

    下一篇:移动APP测试中的功能与非功能测试

网站导航
Copyright(C)51Code软件开发网 2003-2018 , 沪ICP备05003035号