Android单元测试学习记录

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开发人员一窥其中的门路。
  • 所知甚浅,望各位指教
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值