從一開始學(xué)-Android 單元測(cè)試01

想要做單元測(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竖配。

依賴隔離

依賴隔離,這是單元測(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

junitmockito只運(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)

  1. 很多時(shí)候暗挑,我們的代碼不方便寫測(cè)試用例笋除,并不是單元測(cè)試不適用。而是你的代碼耦合度較高炸裆,需要優(yōu)化了... 編寫單元測(cè)試垃它,同時(shí)也能幫助提高代碼質(zhì)量,降低耦合烹看。
  2. 我們要對(duì)SharedPreferencesHelper中的SharedPreferences隔離国拇,可以將SharedPreferences作為構(gòu)造參數(shù)傳入Hepler類。只需要在外部模擬一個(gè)SharedPreferences傳入即可惯殊。請(qǐng)注意Editor也是模擬的酱吝,這些模擬類的行為,可以用when(××).thenreturn(××)等方法來設(shè)置土思,字面意思:“當(dāng)××調(diào)用時(shí)务热,返回××”。
  3. verify()方法己儒,是驗(yàn)證mock對(duì)象是否調(diào)用崎岂,調(diào)用了幾次;

小結(jié)

Junit 和 ** Mockito**基本介紹完了闪湾,注意他們只能在Java環(huán)境測(cè)試冲甘,不要用androidTestCompile依賴。

下一章我們將介紹,Android環(huán)境中的單元測(cè)試江醇。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末濒憋,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子陶夜,更是在濱河造成了極大的恐慌凛驮,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,110評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件律适,死亡現(xiàn)場(chǎng)離奇詭異辐烂,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)捂贿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門纠修,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人厂僧,你說我怎么就攤上這事扣草。” “怎么了颜屠?”我有些...
    開封第一講書人閱讀 165,474評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵辰妙,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我甫窟,道長(zhǎng)密浑,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,881評(píng)論 1 295
  • 正文 為了忘掉前任粗井,我火速辦了婚禮尔破,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘浇衬。我一直安慰自己懒构,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評(píng)論 6 392
  • 文/花漫 我一把揭開白布耘擂。 她就那樣靜靜地躺著胆剧,像睡著了一般。 火紅的嫁衣襯著肌膚如雪醉冤。 梳的紋絲不亂的頭發(fā)上秩霍,一...
    開封第一講書人閱讀 51,698評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音蚁阳,去河邊找鬼前域。 笑死,一個(gè)胖子當(dāng)著我的面吹牛韵吨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,418評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼归粉,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼椿疗!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起糠悼,我...
    開封第一講書人閱讀 39,332評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤届榄,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后倔喂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體铝条,經(jīng)...
    沈念sama閱讀 45,796評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評(píng)論 3 337
  • 正文 我和宋清朗相戀三年席噩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了班缰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,110評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡悼枢,死狀恐怖埠忘,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情馒索,我是刑警寧澤莹妒,帶...
    沈念sama閱讀 35,792評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站绰上,受9級(jí)特大地震影響旨怠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蜈块,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評(píng)論 3 331
  • 文/蒙蒙 一鉴腻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧疯趟,春花似錦拘哨、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至盹舞,卻和暖如春产镐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背踢步。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工癣亚, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人获印。 一個(gè)月前我還...
    沈念sama閱讀 48,348評(píng)論 3 373
  • 正文 我出身青樓述雾,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子玻孟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容