Android单元测试学习记录

基于android-testing - github

环境:IDE:Android Studio 1.5 RC1compileSdkVersion 23buildToolsVersion '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;@SmallTestpublic 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任务时,会抛出异常。

运行

  • 验证通过的情况
    Android单元测试学习记录_第1张图片
  • 更改参数后,验证不通过的情况
    Android单元测试学习记录_第2张图片

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注解标识的属性表明,这是我们“伪造”的对象,我们可以让这些“伪造”的对象表现出我们想要的行为。

@Beforepublic 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().thenReturn()中指定的结果。

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的方法。

@Testpublic 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。
Android单元测试学习记录_第3张图片
当我们的结果和预期的不同(修改了返回的email的值)。
Android单元测试学习记录_第4张图片

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

总结

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

更多相关文章

  1. android中常见的二种数据解析方法----XML和Json
  2. Android SDK Manager无法更新解决方法
  3. Android Timer 更好方法
  4. 删除android ScrollView边界阴影方法
  5. Android显示网络图片相关实现方法浅谈
  6. android 中Drawable跟Bitmap转换及常用于图片相关操作方法 - And
  7. Android屏幕自适应的四种方法
  8. Android中隐藏ActionBar的方法

随机推荐

  1. Android无线管理
  2. Kotlin编程之Glide v4 Generated API(Unr
  3. Android图片加载框架Picasso最全使用教程
  4. android 成长 UI 学习之 Activity 透明,半
  5. XML属性
  6. ANR Bug分析
  7. android--使用Struts2服务端与android交
  8. 关于Android界面编程与视图(View)组件
  9. 【转】Android-Action Bar使用方法
  10. Android GDI之SurfaceFlinger