想要做單元測(cè)試苇侵,第一步先給自己洗腦森书,相信單元測(cè)試是牛逼的布轿,然后在開始學(xué)習(xí)掺冠。
洗腦的雞湯文,后續(xù)再補(bǔ)上冠绢。
基本單元測(cè)試框架
Java單元測(cè)試框架:Junit抚吠、Mockito等;
Android單元測(cè)試框:Robolectric弟胀、AndroidJUnitRunner楷力、Espresso等。
網(wǎng)上一系列單元測(cè)試的文章孵户,最近學(xué)了一遍發(fā)現(xiàn)萧朝,大部分文章中的版本太老,練習(xí)中會(huì)報(bào)錯(cuò)夏哭。
踩完若干個(gè)坑后检柬,終于發(fā)現(xiàn)了google官方就有標(biāo)準(zhǔn)的Samples,讓我們跟著google的大牛學(xué)Unit Testing竖配。
- 鏈接:android-testing-support-library(里面包含了android單元測(cè)試的所有框架何址,demo)
依賴隔離
依賴隔離,這是單元測(cè)試中一個(gè)非常重要的概念进胯,一個(gè)單元的代碼用爪,通常會(huì)有各種依賴。寫單元測(cè)試時(shí)胁镐,應(yīng)該把這些依賴隔離偎血,讓每個(gè)單元保持獨(dú)立。不然任何依賴的報(bào)錯(cuò)盯漂,都會(huì)影響單元測(cè)試的結(jié)果颇玷。
例如:Java環(huán)境下測(cè)試Android業(yè)務(wù)代碼,我們需要將Android的API隔離出去就缆。
Junit & Mockito
junit 和 mockito只運(yùn)行在jvm上亚隙,所以只能測(cè)試純Java。
- Junit:包含一系列斷言方法违崇,測(cè)試函數(shù)異常
- Mockito:一個(gè)體驗(yàn)很好的mocking框架,可以生成模擬對(duì)象诊霹,將外部的依賴隔離開羞延,保持每個(gè)單元的獨(dú)立。
先來演示官方github的demo:
android-testing/unit/BasicSample
添加依賴Junit 脾还,Mockito
testCompile 'junit:junit:4.12'
testCompile "org.mockito:mockito-core:1.10.19"
Code:
public class EmailValidator implements TextWatcher {
......
public static boolean isValidEmail(CharSequence email) {
return email != null && EMAIL_PATTERN.matcher(email).matches();
}
......
}
Test Case:
import org.junit.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
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_NullEmail_ReturnsFalse() {
assertFalse(EmailValidator.isValidEmail(null));
}
}
以上是EmailValidator類和測(cè)試用例伴箩,EmailValidatorTest運(yùn)行后,所有被@Test注釋的方法都會(huì)被執(zhí)行鄙漏。上面的代碼嗤谚,用Junit的測(cè)試了isValidEmail()方法棺蛛,在不同條件下的返回狀態(tài)。
Code:
public class SharedPreferenceEntry {
private final String mName;
private final Calendar mDateOfBirth;
private final String mEmail;
......
}
public class SharedPreferencesHelper {
private final SharedPreferences mSharedPreferences;
public SharedPreferencesHelper(SharedPreferences sharedPreferences) {
mSharedPreferences = sharedPreferences;
}
public boolean savePersonalInfo(SharedPreferenceEntry sharedPreferenceEntry){
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putString(KEY_NAME, sharedPreferenceEntry.getName());
editor.putLong(KEY_DOB, sharedPreferenceEntry.getDateOfBirth().getTimeInMillis());
editor.putString(KEY_EMAIL, sharedPreferenceEntry.getEmail());
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);
}
}
Test Case:
@RunWith(MockitoJUnitRunner.class)
public class SharedPreferencesHelperTest {
private SharedPreferenceEntry mSharedPreferenceEntry;
private SharedPreferencesHelper mMockSharedPreferencesHelper;
SharedPreferences mMockSharedPreferences;
@Mock
SharedPreferences.Editor mMockEditor;
@Before //顧名思義巩步,在程序開始的時(shí)候運(yùn)行旁赊,初始化一些數(shù)據(jù)
public void initMocks() {
//生成模擬對(duì)象,可以調(diào)用mock()方法椅野。也可以用注釋@Mock终畅。這里分別演示。
mMockBrokenSharedPreferences = mock(SharedPreferences.class);
mSharedPreferenceEntry = new SharedPreferenceEntry(TEST_NAME, TEST_DATE_OF_BIRTH, TEST_EMAIL);
mMockSharedPreferencesHelper = createMockSharedPreference();
}
private SharedPreferencesHelper createMockSharedPreference() {
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());
when(mMockEditor.commit()).thenReturn(true);
when(mMockSharedPreferences.edit()).thenReturn(mMockEditor);
return new SharedPreferencesHelper(mMockSharedPreferences);
}
@Test
public void sharedPreferencesHelper_SaveAndReadPersonalInformation() {
boolean success = mMockSharedPreferencesHelper.savePersonalInfo(mSharedPreferenceEntry);
verify(mMockEditor).commit();//驗(yàn)證是否調(diào)用
verify(mMockEditor,times(1)).commit();//驗(yàn)證是否調(diào)用1次
assertThat("Checking that SharedPreferenceEntry.save... returns true",
success, is(true));
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())));
}
}
以上代碼估計(jì)一臉蒙蔽竟闪,簡(jiǎn)單用法別的博客都講了离福。這里深入點(diǎn)來講。
演示了Mockito的用法炼蛤,生成SharedPreferences妖爷,Editor的模擬類,通過構(gòu)造方法傳入Helper封裝類中理朋。對(duì)Android API依賴隔離絮识,確保對(duì)SharedPreferencesHelper類的單元測(cè)試。
注意點(diǎn):
- 很多時(shí)候暗挑,我們的代碼不方便寫測(cè)試用例笋除,并不是單元測(cè)試不適用。而是你的代碼耦合度較高炸裆,需要優(yōu)化了... 編寫單元測(cè)試垃它,同時(shí)也能幫助提高代碼質(zhì)量,降低耦合烹看。
- 我們要對(duì)SharedPreferencesHelper中的SharedPreferences隔離国拇,可以將SharedPreferences作為構(gòu)造參數(shù)傳入Hepler類。只需要在外部模擬一個(gè)SharedPreferences傳入即可惯殊。請(qǐng)注意Editor也是模擬的酱吝,這些模擬類的行為,可以用when(××).thenreturn(××)等方法來設(shè)置土思,字面意思:“當(dāng)××調(diào)用時(shí)务热,返回××”。
- verify()方法己儒,是驗(yàn)證mock對(duì)象是否調(diào)用崎岂,調(diào)用了幾次;
小結(jié)
Junit 和 ** Mockito**基本介紹完了闪湾,注意他們只能在Java環(huán)境測(cè)試冲甘,不要用androidTestCompile依賴。
下一章我們將介紹,Android環(huán)境中的單元測(cè)試江醇。