您的位置:首页 > 移动开发 > Android开发

Android单元测试学习记录

2015-11-20 15:08 316 查看

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包内的类

EmailValidatorTest
EmailValidator
逻辑的单元测试

SharedPreferencesHelperTest
SharedPreferencesHelper
的单元测试,并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);
}


这个方法创造出了一个正确响应的
SharedPreferences
SharedPreferences.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()
方法所做的目的,就是想知道,在执行
SharedPreferencesHelper
savePersonalInfo()
方法和
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。



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



当然这么弱智的Bug,各位都不会写出来。但是我们的目的,就是为了找出不容易出现的Bug。

后面还有一个方法
sharedPreferencesHelper_SavePersonalInformationFailed_ReturnsFalse()
,这个方法是来验证我们
@Mock
的坏掉的那个
mMockBrokenSharedPreferencesHelper
,看在错误的情况下,会不会得到正确的错误结果,这个有点绕。

总结

首先明确我们要验证的对象,如果这个对象依赖于我们无法直接创建的类,那么我们可以Mock出来,即以上例子中的
SharedPreferences
SharedPreferences.Editor
,这两个对象我们是没法像创建一个实体类一样创建,因此,我们可以“假装”创建一个,并且规定我们需要的响应。然后我们就可以关注于我们需要测试的
SharedPreferencesHelper
这个类之中方法的逻辑,是否存在问题。

其中涉及的具体技术,大家可以去阅读其他的文章,在这里抛砖引玉,希望给不太了解单元测试的Android开发人员一窥其中的门路。

所知甚浅,望各位指教
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: