如何在Android中進(jìn)行本地單元測試

Android上的測試種類

  1. Local Unit Test

在本機(jī)的Java VM上運(yùn)行

  • 優(yōu)點(diǎn):運(yùn)行速度快,jenkins每次編譯時(shí)可以運(yùn)行,適合做TDD
  • 缺點(diǎn):不能測試android代碼

2.Intrumented Test

運(yùn)行的時(shí)候生成測試apk和被測應(yīng)用的apk,安裝到手機(jī)上運(yùn)行

-優(yōu)點(diǎn):所有代碼都能測試
-缺點(diǎn):運(yùn)行速度慢,不能在jenkins編譯時(shí)運(yùn)行

1.png

Local Unit Test##

代碼位置

在src下創(chuàng)建test文件夾,如果測試代碼是針對某個(gè)flavor和build type的,則創(chuàng)建testFlavorBuildType的文件夾

2.png

在gradle中設(shè)置


testCompile 'junit:junit:4.12'

生成測試代碼

打開要測試類的代碼,選中類的名字,ctrl+shift+T

3.png

選擇要測試的方法,以及setUp和tearDown方法

4.png

生成的測試類

5.png

Junit 4注解

標(biāo)注 說明
@Before 標(biāo)注setup方法,每個(gè)單元測試用例方法調(diào)用之前都會(huì)調(diào)用
@After 標(biāo)注teardown方法,每個(gè)單元測試用例方法調(diào)用之后都會(huì)調(diào)用
@Test 標(biāo)注的每個(gè)方法都是一個(gè)測試用例
@BeforeClass 標(biāo)注的靜態(tài)方法,在當(dāng)前測試類所有用例方法執(zhí)行之前執(zhí)行
@AfterClass 標(biāo)注的靜態(tài)方法,在當(dāng)前測試類所有用例方法執(zhí)行之后執(zhí)行
@Test(timeout=) 為測試用例指定超時(shí)時(shí)間
6.png

斷言

Junit提供了一系列斷言來判斷是pass還是fail

方法 說明
assertTrue(condition) condition為真pass,否則fail
assertFalse(condition) condition為假pass,否則fail
fail() 直接fail
assertEquals(expected, actual) expected equal actual pass,否則fail
assertSame(expected, actual) expected == actual pass,否則fail

更多方法查看Assert.java的靜態(tài)方法

運(yùn)行測試

IDE:點(diǎn)擊邊欄的箭頭可以運(yùn)行整個(gè)測試類或單個(gè)方法

7.png

Command line:gradle test,運(yùn)行整個(gè)unit test

測試代碼覆蓋率

android gradle 插件自帶jacoco進(jìn)行代碼測試覆蓋率的統(tǒng)計(jì)

根build.gralde配置

buildscript {
...
    dependencies {
        ....
        classpath 'com.dicedmelon.gradle:jacoco-android:0.1.1'
    }
}

模塊build.gradle配置

apply plugin: 'jacoco-android'

buildTypes {
    debug {
        testCoverageEnabled = true
    }
}

//需要排除在統(tǒng)計(jì)之外的類
jacocoAndroidUnitTestReport {
    excludes += ['**/ApiConnectionImpl**']
}

cmd:

  • gradle jacocoTestReport 生成全部flavor和buildtype的測試覆蓋率報(bào)告
  • gradle jacocoTestFlavorBuildTypeUnitTestReport 指定flavor和buildtype的測試覆蓋率報(bào)告

生成的報(bào)告放在

module\build\reports\jacoco\jacocoTestFlavorBuildTypeUnitTestReport\html

8.png

Local unit test help libary

Hamcrest

testCompile 'org.hamcrest:hamcrest-library:1.3'

為斷言提供更好的可讀性(更接近與自然語言)

可讀性

Junit:

assertEquals(expected, actual);

Hamcrest:

assertThat(actual, is(equalTo(expected)));

Junit:

assertFalse(expected.equals(actual));

Hamcrest:


assertThat(actual, is(not(equalTo(expected))));

失敗信息更加詳細(xì)

Junit

//AssertionError里沒有expected和actual的信息
assertTrue(expected.contains(actual));
java.lang.AssertionError at ...

Hamcrest

assertThat(actual, containsString(expected));
java.lang.AssertionError:
Expected: a string containing "abc"
got: "def"

Assert 條件更加靈活

可以將多個(gè)assert條件通過anyOf() (或), allOf()(與)組合在一起

assertThat("test", anyOf(is("test2"), containsString("te")));

More Info

http://www.vogella.com/tutorials/Hamcrest/article.html

Mockito

testCompile "org.mockito:mockito-core:2.2.0"

Mock所需要測試的類,可以指定方法的返回值

兩種方式創(chuàng)建mock

1.注解,這種方式必須添加MockitoRule

public class MockitoTest  {

        @Mock
        MyDatabase databaseMock; 

        @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); 

        @Test
        public void testQuery()  {
                ClassToTest t  = new ClassToTest(databaseMock); 
                boolean check = t.query("* from t"); 
                assertTrue(check); 
                verify(databaseMock).query("* from t"); 
        }
}

2.代碼創(chuàng)建mock

@Test
public void test1()  {
        //  create mock
        MyClass test = Mockito.mock(MyClass.class);

        // define return value for method getUniqueId()
        when(test.getUniqueId()).thenReturn(43);

        // use mock in test....
        assertEquals(test.getUniqueId(), 43);
}

驗(yàn)證方法的調(diào)用

@Test
public void testVerify()  {
        // create and configure mock
        MyClass test = Mockito.mock(MyClass.class);
        when(test.getUniqueId()).thenReturn(43);

        // call method testing on the mock with parameter 12
        test.testing(12);
        test.getUniqueId();
        test.getUniqueId();

        // now check if method testing was called with the parameter 12
        verify(test).testing(Matchers.eq(12));

        // was the method called twice?
        verify(test, times(2)).getUniqueId();

        // other alternatives for verifiying the number of method calls for a method
        verify(mock, never()).someMethod("never called");
        verify(mock, atLeastOnce()).someMethod("called at least once");
        verify(mock, atLeast(2)).someMethod("called at least twice");
        verify(mock, times(5)).someMethod("called five times");
        verify(mock, atMost(3)).someMethod("called at most 3 times");
}

Spy vs Mock

對真正對象的包裝,所有調(diào)用會(huì)調(diào)用真正對象的方法,但是會(huì)記錄方法調(diào)用的信息

調(diào)用方法:@Spy or Mockito.spy()

@Test
public void whenSpyingOnList_thenCorrect() {
    List<String> list = new ArrayList<String>();
    List<String> spyList = Mockito.spy(list);
 
    spyList.add("one");
    spyList.add("two");
 
    Mockito.verify(spyList).add("one");
    Mockito.verify(spyList).add("two");
 
    assertEquals(2, spyList.size());
}

Spy也可以覆蓋真正對象的方法:

@Test
public void whenStubASpy_thenStubbed() {
    List<String> list = new ArrayList<String>();
    List<String> spyList = Mockito.spy(list);
 
    assertEquals(0, spyList.size());
 
    Mockito.doReturn(100).when(spyList).size();
    assertEquals(100, spyList.size());
}

The mock simply creates a bare-bones shell instance of the Class, entirely instrumented to track interactions
with it.

On the other hand, the spy will wrap an existing instance. It will still behave in the same way as the normal
instance – the only difference is that it will also be instrumented to track all the interactions with it.

依賴注入

為了方便測試,一個(gè)類對外部類的依賴需要通過某種方式傳入,而不是在類的內(nèi)部創(chuàng)建依賴

public class MyClass {
    Foo foo;
    Boo boo;

    public MyClass() {
        foo = new Foo(); //依賴在內(nèi)部創(chuàng)建,無法mock
        boo = new Boo();
    }
}
public class MyClass {
    Foo foo;
    Boo boo;

    public MyClass(Foo foo, Boo boo) {
        this.foo = foo; //依賴注入,測試MyClass時(shí)可以傳入Mock的foo和boo
        this.boo = boo;
    }
}

More Info

http://www.baeldung.com/mockito-spy

http://www.vogella.com/tutorials/Mockito/article.html

Robolectric

testCompile "org.robolectric:robolectric:3.1.1"

Robolectric is designed to allow you to test Android applications on the JVM based on the JUnit 4 framework. Robolectric is a framework that allows you to write unit tests and run them on a desktop JVM while still using Android API. Robolectric mocks part of the Android framework contained in the android.jar file. Robolectric provides also implementations for the methods while the standard Android unit testing support throws exceptions for all Android methods.

This enables you to run your Android tests in your continuous integration environment without any additional setup. Robolectric supports resource handling, e.g., inflation of views. You can also use the findViewById() to search in a view

如何使用

在test之前使用RunWith注解

@RunWith(RobolectricGradleTestRunner.class)

在@Config中配置測試參數(shù)

@Config(sdk = Build.VERSION_CODES.JELLY_BEAN(default 16), 
    application = CustomApplication.class, 
    manifest = "some/build/path/AndroidManifest.xml")

在測試運(yùn)行時(shí),robolectric根據(jù)你要測試的api level從maven倉庫中拉取對應(yīng)android api實(shí)現(xiàn)jar包,如果你的機(jī)器上網(wǎng)絡(luò)不太好,可以將這些jar先下載到本地,然后啟用offline模式

android {
  testOptions {
    unitTests.all {
      systemProperty 'robolectric.offline', 'true'
      systemProperty 'robolectric.dependency.dir', "somewhere you place your jar"
    }
  }
}
9.png

示例代碼:

@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class)
public class MyActivityTest {

        private MainActivity activity;

        @Test
        public void shouldHaveHappySmiles() throws Exception {
                String hello = new MainActivity().getResources().getString(
                                R.string.hello_world);
                assertThat(hello, equalTo("Hello world!"));
        }

        @Before
        public void setup()  {
                activity = Robolectric.buildActivity(MainActivity.class)
                                .create().get();
        }
        @Test
        public void checkActivityNotNull() throws Exception {
                assertNotNull(activity);
        }

        @Test
        public void buttonClickShouldStartNewActivity() throws Exception
        {
            Button button = (Button) activity.findViewById( R.id.button2 );
            button.performClick();
            Intent intent = Shadows.shadowOf(activity).peekNextStartedActivity();
            assertEquals(SecondActivity.class.getCanonicalName(), intent.getComponent().getClassName());
        }

        @Test
        public void testButtonClick() throws Exception {
                MainActivity activity = Robolectric.buildActivity(MainActivity.class)
                                .create().get();
                Button view = (Button) activity.findViewById(R.id.button1);
                assertNotNull(view);
                view.performClick();
                assertThat(ShadowToast.getTextOfLatestToast(), equalTo("Lala") );
        }

}

More Info

http://robolectric.org/

http://www.vogella.com/tutorials/Robolectric/article.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

  • 一.基本介紹 背景: 目前處于高速迭代開發(fā)中的Android項(xiàng)目往往需要除黑盒測試外更加可靠的質(zhì)量保障,這正是單元...
    anmi7閱讀 2,177評論 0 6
  • Android單元測試介紹 處于高速迭代開發(fā)中的Android項(xiàng)目往往需要除黑盒測試外更加可靠的質(zhì)量保障,這正是單...
    東經(jīng)315度閱讀 3,422評論 6 37
  • 1.概述 這里你將學(xué)習(xí)如何在Android Studio中配置工程用于測試,在開發(fā)機(jī)器上編寫并運(yùn)行單元測試,以及如...
    帥鵬帥非常帥閱讀 4,444評論 0 2
  • @Author:彭海波 前言 單元測試(又稱為模塊測試, Unit Testing)是針對程序模塊(軟件設(shè)計(jì)的最小...
    海波筆記閱讀 5,117評論 0 52
  • 做開發(fā)這么長時(shí)間了,對app測試一直沒有深入研究過。平時(shí)開發(fā)完后,都是自己根據(jù)原型操作一下,沒有太大問題就交給其他...
    皇馬船長閱讀 6,619評論 0 6

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