关闭

Android单元测试学习记录

标签: android单元测试junitmockio
520人阅读 评论(0) 收藏 举报
分类:

Android单元测试学习记录

基于android-testing - github

环境:
IDE:Android Studio 1.5 RC1
compileSdkVersion 23
buildToolsVersion '23.0.1'
targetSdkVersion 23

依赖:
testCompile 'junit:junit:4.12'
testCompile 'org.mockito:mockito-core:1.10.19'

classpath 'com.android.tools.build:gradle:1.3.1'

Basic Sample

main包内的类

  • EmailValidator TextWatcher的实现,验证E-mail地址是否合法
  • MainActivity 一个Activity…
  • SharedPreferenceEntry 一个实体类
  • SharedPreferencesHelper SharedPreferences的操作封装

test包内的类

  • EmailValidatorTestEmailValidator逻辑的单元测试
  • SharedPreferencesHelperTestSharedPreferencesHelper的单元测试,并Mock(后续介绍)SharedPreferences
Mock

mock测试就是在测试过程中,对于某些不容易构造或者不容易获取的对象,用一个虚拟的对象来创建以便测试的测试方法。 —百科Mock测试

本例中,使用的Mock工具为mockito,以下根据代码,对其使用作出解释,具体的使用,请读者自行查阅资料。

main的代码是基础,如果不明白的话,请先弄明白再学习单元测试

EmailValidatorTest 解析

import android.test.suitebuilder.annotation.SmallTest;

import org.junit.Test;

import java.util.regex.Pattern;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

@SmallTest
public class EmailValidatorTest {


    @Test
    public void emailValidator_CorrectEmailSimple_ReturnsTrue() {
        assertTrue(EmailValidator.isValidEmail("name@email.com"));
    }

    @Test
    public void emailValidator_CorrectEmailSubDomain_ReturnsTrue() {
        assertTrue(EmailValidator.isValidEmail("name@email.co.uk"));
    }

    @Test
    public void emailValidator_InvalidEmailNoTld_ReturnsFalse() {
        assertFalse(EmailValidator.isValidEmail("name@email"));
    }

    @Test
    public void emailValidator_InvalidEmailDoubleDot_ReturnsFalse() {
        assertFalse(EmailValidator.isValidEmail("name@email..com"));
    }

    @Test
    public void emailValidator_InvalidEmailNoUsername_ReturnsFalse() {
        assertFalse(EmailValidator.isValidEmail("@email.com"));
    }

    @Test
    public void emailValidator_EmptyString_ReturnsFalse() {
        assertFalse(EmailValidator.isValidEmail(""));
    }

    @Test
    public void emailValidator_NullEmail_ReturnsFalse() {
        assertFalse(EmailValidator.isValidEmail(null));
    }
}
  • 类名上的类注解@smallTest
类别 用途
@SmallTest 测试代码中不与任何的文件系统或网络交互
@MediumTest 测试代码中访问测试用例运行时所在的设备的文件系统
@LargeTest 测试代码中访问外部的文件系统或网络

* 方法注解@Test
每个被该注解标识的方法,均会在执行test任务中被调用

  • assertTrue assertTrue为断言表达式的返回值,如果断言与结果相同,那么不会有异常;如果不同,在执行test任务时,会抛出异常。

运行

  • 验证通过的情况
    通过
  • 更改参数后,验证不通过的情况
    不通过

SharedPreferencesHelperTest解析

这个类比较长,一段一段来,首先看类注解

    @SmallTest
    @RunWith(MockitoJUnitRunner.class)

@RunWith 表示该测试用例运行在某个环境下,在本例中,即让我们运行在Mockito的环境下,让我们可以“假冒一些行为”,以下会介绍。

    private static final String TEST_NAME = "Test name";

    private static final String TEST_EMAIL = "test@email.com";

    private static final Calendar TEST_DATE_OF_BIRTH = Calendar.getInstance();

    static {
        TEST_DATE_OF_BIRTH.set(1980, 1, 1);
    }

    private SharedPreferenceEntry mSharedPreferenceEntry;

    private SharedPreferencesHelper mMockSharedPreferencesHelper;

    private SharedPreferencesHelper mMockBrokenSharedPreferencesHelper;

    @Mock
    SharedPreferences mMockSharedPreferences;

    @Mock
    SharedPreferences mMockBrokenSharedPreferences;

    @Mock
    SharedPreferences.Editor mMockEditor;

    @Mock
    SharedPreferences.Editor mMockBrokenEditor; 

以上是对成员变量的声明,被@Mock注解标识的属性表明,这是我们“伪造”的对象,我们可以让这些“伪造”的对象表现出我们想要的行为。

@Before
public void initMocks() {
   // 创建一个SharedPreferenceEntry实体
   mSharedPreferenceEntry = new SharedPreferenceEntry(TEST_NAME, TEST_DATE_OF_BIRTH,
           TEST_EMAIL);

   // 创建一个“仿造”的SharedPreferences.
   mMockSharedPreferencesHelper = createMockSharedPreference();

   // 创建一个“仿造”的SharedPreferences,但是这个会返回失败的结果.
   mMockBrokenSharedPreferencesHelper = createBrokenMockSharedPreference();
    }
  • @Before 此注解标识的方法需要在执行所有@Test之前执行,可以理解为初始化所有需要的资源
  • createMockSharedPreference()createBrokenMockSharedPreference()创建我们需要的Mock对象,下面来看这两个方法。
private SharedPreferencesHelper createMockSharedPreference() {
        // 假装读SharedPreferences的时候mMockSharedPreferences被正确的写入过
        when(mMockSharedPreferences.getString(eq(SharedPreferencesHelper.KEY_NAME), anyString()))
                .thenReturn(mSharedPreferenceEntry.getName());
        when(mMockSharedPreferences.getString(eq(SharedPreferencesHelper.KEY_EMAIL), anyString()))
                .thenReturn(mSharedPreferenceEntry.getEmail());
        when(mMockSharedPreferences.getLong(eq(SharedPreferencesHelper.KEY_DOB), anyLong()))
                .thenReturn(mSharedPreferenceEntry.getDateOfBirth().getTimeInMillis());

        // “假装”有一个正确的commit()返回
        when(mMockEditor.commit()).thenReturn(true);

        // 返回mMockEditor 当 调用mMockSharedPreferences.edit()
        when(mMockSharedPreferences.edit()).thenReturn(mMockEditor);
        return new SharedPreferencesHelper(mMockSharedPreferences);
    }

这个方法创造出了一个正确响应的SharedPreferencesSharedPreferences.Editor对象,当在@Test修饰的方法中调用@Mock修饰的对象的方法的时候,会返回when(<Invoke Method>).thenReturn(<Result>)中指定的结果。

private SharedPreferencesHelper createBrokenMockSharedPreference() {
   // 假定mMockBrokenEditor.commit()返回false
   when(mMockBrokenEditor.commit()).thenReturn(false);

   // mMockBrokenSharedPreferences.edit()返回”坏掉的“Editor
   when(mMockBrokenSharedPreferences.edit()).thenReturn(mMockBrokenEditor);
   return new SharedPreferencesHelper(mMockBrokenSharedPreferences);
}

以上的两个方法就是预设@Mock的对象的行为。具体关于when().thenReturn()的使用,请查阅Mockito的使用。在执行完@Before的方法之后,下面看@Test的方法。

@Test
public void sharedPreferencesHelper_SaveAndReadPersonalInformation() {
   // 保存实体的数据到SharePreferences,这个方法下面会贴出来
   boolean success = mMockSharedPreferencesHelper.savePersonalInfo(mSharedPreferenceEntry);
    // 第一个参数是这个断言的原因,第二个是被断言的参数,第三个是预期的结果
   assertThat("Checking that SharedPreferenceEntry.save... returns true",
           success, is(true));

   // 将存储在SharePreferences中的数据取出。其实是调用了之前声明的when..thenReturn
   SharedPreferenceEntry savedSharedPreferenceEntry =
           mMockSharedPreferencesHelper.getPersonalInfo();

   // 验证存储读取的结果是否正确
   assertThat("Checking that SharedPreferenceEntry.name has been persisted and read correctly",
           mSharedPreferenceEntry.getName(),
           is(equalTo(savedSharedPreferenceEntry.getName())));
   assertThat("Checking that SharedPreferenceEntry.dateOfBirth has been persisted and read "
           + "correctly",
           mSharedPreferenceEntry.getDateOfBirth(),
           is(equalTo(savedSharedPreferenceEntry.getDateOfBirth())));
   assertThat("Checking that SharedPreferenceEntry.email has been persisted and read "
           + "correctly",
           mSharedPreferenceEntry.getEmail(),
           is(equalTo(savedSharedPreferenceEntry.getEmail())));
}

对我们需要测试的SharedPreferencesHelper的两个方法savePersonalInfo()getPersonalInfo()进行调用,看其会不会返回预期的结果,如果任何一个assertThat方法调用不符合预期,那么会抛出异常,稍后展示,我们来看SharedPreferencesHelper的两个方法实现。

public boolean savePersonalInfo(SharedPreferenceEntry sharedPreferenceEntry){
   // mSharedPreferences.edit()这个操作是我们预先规定的,返回Mock对象
   SharedPreferences.Editor editor = mSharedPreferences.edit();
   // 以下的三行,mMockEditor并有没有做出规定,相当于没有执行
   editor.putString(KEY_NAME, sharedPreferenceEntry.getName());
   editor.putLong(KEY_DOB, sharedPreferenceEntry.getDateOfBirth().getTimeInMillis());
   editor.putString(KEY_EMAIL, sharedPreferenceEntry.getEmail());

   // 预先规定,返回true
   return editor.commit();
}

public SharedPreferenceEntry getPersonalInfo() {
   // 以下返回我们预先规定的结果
   String name = mSharedPreferences.getString(KEY_NAME, "");
   Long dobMillis =
           mSharedPreferences.getLong(KEY_DOB, Calendar.getInstance().getTimeInMillis());
   Calendar dateOfBirth = Calendar.getInstance();
   dateOfBirth.setTimeInMillis(dobMillis);
   String email = mSharedPreferences.getString(KEY_EMAIL, "");

   // 组装成我们需要的对象
   return new SharedPreferenceEntry(name, dateOfBirth, email);
}

sharedPreferencesHelper_SaveAndReadPersonalInformation()方法所做的目的,就是想知道,在执行SharedPreferencesHelpersavePersonalInfo()方法和getPersonalInfo()会不会出问题。读者可能会有疑问,这两个方法里,要么是我们已经规定了结果的,要么就是执行了也不会有任何影响的,那测试有什么用呢?好,那么我们添加一个Bug,如下:

public SharedPreferenceEntry getPersonalInfo() {
   String name = mSharedPreferences.getString(KEY_NAME, "");
   Long dobMillis =
           mSharedPreferences.getLong(KEY_DOB, Calendar.getInstance().getTimeInMillis());
   Calendar dateOfBirth = Calendar.getInstance();
   dateOfBirth.setTimeInMillis(dobMillis);
   String email = mSharedPreferences.getString(KEY_EMAIL, "");
   //加入的Bug
    if(editor != null)
        throw new NullPointerException("This is Boring");

   return new SharedPreferenceEntry(name, dateOfBirth, email);
}

当我们加入了Bug。
Bug
当我们的结果和预期的不同(修改了返回的email的值)。
Bug2

当然这么弱智的Bug,各位都不会写出来。但是我们的目的,就是为了找出不容易出现的Bug。
后面还有一个方法sharedPreferencesHelper_SavePersonalInformationFailed_ReturnsFalse(),这个方法是来验证我们@Mock的坏掉的那个mMockBrokenSharedPreferencesHelper,看在错误的情况下,会不会得到正确的错误结果,这个有点绕。

总结

  • 首先明确我们要验证的对象,如果这个对象依赖于我们无法直接创建的类,那么我们可以Mock出来,即以上例子中的SharedPreferencesSharedPreferences.Editor,这两个对象我们是没法像创建一个实体类一样创建,因此,我们可以“假装”创建一个,并且规定我们需要的响应。然后我们就可以关注于我们需要测试的SharedPreferencesHelper这个类之中方法的逻辑,是否存在问题。
  • 其中涉及的具体技术,大家可以去阅读其他的文章,在这里抛砖引玉,希望给不太了解单元测试的Android开发人员一窥其中的门路。
  • 所知甚浅,望各位指教
0
0
查看评论

关于单元测试的学习记录

关于项目单元测试学习总结
  • u014378902
  • u014378902
  • 2016-05-05 23:12
  • 343

C/C++单元测试培训

看视频,快速掌握C/C++单元测试。主讲老师拥有十多年单元测试实践,直接服务过的企业就达上百家。课程抛弃了宽泛而不能落地的理论,直面企业项目的单元测试难题,深入浅出地讲授C/C++单元测试的问题、思路与方法。在此课程的基础上,学员只需要经过一些练习,就可以在实 际的开发中应用单元测试、TDD、ETD...
  • dellfox
  • dellfox
  • 2014-04-17 17:00
  • 3892

Android中如何简单的做单元测试

单元测试 单元测试(模块测试)是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。通常而言,一个单元测试是用于判断某个特定条件(或者场景)下某个特定函数的行为。例如,你可能把一个很大的值放入一个有序list 中去,然后确认该值出现在list 的尾部。或者,你可能会从字...
  • JavaAndroid730
  • JavaAndroid730
  • 2016-11-24 22:51
  • 1902

Android中的单元测试(你有用过吗?O(∩_∩)O~)

前言: 周末真的是除了睡觉还是睡觉啊O(∩_∩)O~,打开博客,看到别人大牛写的东西的时候,感觉差距好大啊,自己要学习的东西太多太多了,不管怎样,现在还是加油吧,骚年~~对Android Studio还不是很熟的,或者是ADT的深度中毒患者的可以去看看这篇文章 因为之前一直用的ADT,才转到AS不...
  • vv_bug
  • vv_bug
  • 2016-11-14 23:13
  • 2175

Android单元测试 - 如何开始?

转载自:http://www.jianshu.com/p/bc99678b1d6e 回顾: 《谈谈为什么写单元测试》 基本单元测试框架 Java单元测试框架:Junit、Mockito、Powermockito等;Android:Robolectric、Android...
  • u011404670
  • u011404670
  • 2017-02-06 12:04
  • 492

Android产品研发(十九)-->Android studio中的单元测试

本文我们将讲解如何在android studio中进行单元测试,其可以很方便的为我们提供功能性测试,所以如果项目中有用到测试数据的时候,可以先进行单元测试,如果可以正常输出数据了,然后再到UI中执行,这样会提高一些工作效率
  • qq_23547831
  • qq_23547831
  • 2016-07-11 21:42
  • 12681

Android 单元测试记录

Espresso 官方文档 https://google.github.io/android-testing-support-library/docs/espresso/index.html Espresso   使用样例 https://androidresearch.wordpre...
  • u010963246
  • u010963246
  • 2016-07-26 18:59
  • 319

Android单元测试时如何使用log查看输出结果

Android单元测试与日志输出        :http://blog.csdn.net/xy849288321/article/details/7054790
  • songsallyjin
  • songsallyjin
  • 2015-08-07 16:12
  • 1097

Android官方MVP项目单元测试

Google在3月份推出了一个项目,用来介绍Android MVP架构的各种组合,可以认为是官方在这方面的最佳实践。令人称道的是除了MVP本身之外,这些工程配备了极其完善的单元测试用例,学习价值极高。本文着重针对todo-mvp的单元测试进行解读。 写在前面 关于MVP 关于MVP的介绍很多...
  • zrbcsdn
  • zrbcsdn
  • 2016-05-03 17:33
  • 3073

单元测试-使用nmock测试你的.NET代码(1)

最近的工作都是在做单元测试,偶尔看到了这个被称为nmock的东西,找了半死也没找出一些关于它的文档,只在MSDN发现了一点资料,尝试着做一下翻译吧(两年多没碰了,肯定会有一些翻译的不好的地方)。原文地址:http://msdn.microsoft.com/msdnmag/issues/04/10/N...
  • gamix
  • gamix
  • 2004-12-17 23:38
  • 999
    个人资料
    • 访问:30791次
    • 积分:408
    • 等级:
    • 排名:千里之外
    • 原创:12篇
    • 转载:0篇
    • 译文:1篇
    • 评论:6条
    文章分类
    最新评论