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

想要做單元測(cè)試,第一步先給自己洗腦,相信單元測(cè)試是牛逼的,然后在開始學(xué)習(xí)。
洗腦的雞湯文,后續(xù)再補(bǔ)上。

基本單元測(cè)試框架

Java單元測(cè)試框架:JunitMockito等;
Android單元測(cè)試框:Robolectric、AndroidJUnitRunnerEspresso等。

網(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ú)立。

先來(lái)演示官方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)來(lái)講。
演示了Mockito的用法,生成SharedPreferences,Editor的模擬類,通過(guò)構(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(××)等方法來(lái)設(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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容