關(guān)于android單元測試的一切

介紹

  • setup:即new 出待測試的類,設(shè)置一些前提條件

  • 執(zhí)行動(dòng)作:即調(diào)用被測類的被測方法,并獲取返回結(jié)果

  • 驗(yàn)證結(jié)果:驗(yàn)證獲取的結(jié)果跟預(yù)期的結(jié)果是一樣的

Junit4

java測試框架

testCompile 'junit:junit:4.12'
@Before
@Test
(expected = IllegalArgumentException.class) 驗(yàn)證是否拋該異常
@After
@Ignore 跑所有測試方法忽略該方法

除了在AndroidStudio里面運(yùn)行,你還可以在命令行通過gradle testDebugUnitTest,或者是gradle testReleaseUnitTest,分別運(yùn)行debug和release版本的unit testing

Mockito的使用

  • 參考 https://segmentfault.com/u/chriszou
    java界使用最廣法的mock框架
    概念就是創(chuàng)建一個(gè)類的虛假對象,在測試環(huán)境中,用來替換掉真實(shí)的

  • 添加依賴

repositories { jcenter() }
dependencies {
      testCompile "org.mockito:mockito-core:
}
  • api
  1. 驗(yàn)證方法調(diào)用:
    Mockito.verify(objectToVerify,Mockito.times(3)).methodToVerify(Mockito.anyString());
  1. 設(shè)定返回值,方便測試:
    Mockito.when(mockObject.targetMethod(args)).thenReturn(desiredReturnValue);
  2. 執(zhí)行特定動(dòng)作,如訪問網(wǎng)絡(luò)數(shù)據(jù)
Mockito.doAnswer(desiredAnswer).when(mockObject).targetMethod(args);
Mockito.doAnswer(new Answer() {
   @Override
   public Object answer(InvocationOnMock invocation) throws Throwable {
       //這里可以獲得傳給performLogin的參數(shù)
       Object[] arguments = invocation.getArguments();

       //callback是第三個(gè)參數(shù)
       NetworkCallback callback = (NetworkCallback) arguments[2];

       callback.onFailure(500, "Server error");
       return 500;
   }
}).when(mockUserManager).performLogin(anyString(), anyString(), any(NetworkCallback.class));

ps: Mockito.spy與mock的唯一區(qū)別就是默認(rèn)行為不一樣:spy對象的方法默認(rèn)調(diào)用真實(shí)的邏輯,mock對象的方法默認(rèn)什么都不做,或直接返回默認(rèn)值。

dagger2

能不用就不用!

  • 依賴
buildscript {
    ...
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.2'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
        //添加apt插件
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
}

...
//應(yīng)用apt插件
apply plugin: 'com.neenbedankt.android-apt'

...

dependencies {
    ...
    //引入dagger2
    compile 'com.google.dagger:dagger:2.4'
    apt 'com.google.dagger:dagger-compiler:2.4'
    //java注解
    provided 'org.glassfish:javax.annotation:10.0-b28'
}
  • 依賴注入概念
    我們平常使用的構(gòu)造函數(shù),需要傳入對象去使用,就算是一種依賴注入,然而當(dāng)我們需要的對象嵌套越來越多的時(shí)候,就會顯得冗余,這個(gè)時(shí)候dagger2就出現(xiàn)了。

client --> Component(工廠管理員)-->Module(生產(chǎn)依賴的工廠)-->Dependency

  • api
    Module
    @Module 修飾類
    @Provides 修飾方法
    @Singleton 修飾方法,獲取單例節(jié)省資源
    Component
    @Component
    (modules={OneModule.class}) 修飾類,指定管理的生產(chǎn)Dependency的工廠
    1. 定義返回Dependency的抽象方法即可
    2. 先在Component 定義inject(Object obj方法
    定義成員變量@Inject修飾即可
    ps: 不能繼承,都要實(shí)現(xiàn)inject(this)方法才能生成實(shí)例

  • 單元測試

//需要測試
public class LoginActivity extends AppCompatActivity {
    @Inject
    LoginPresenter mLoginPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ComponentHolder.getAppComponent().inject(this);

        findViewById(R.id.login).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                String username = ((EditText) findViewById(R.id.username)).getText().toString();
                String password = ((EditText) findViewById(R.id.password)).getText().toString();

                mLoginPresenter.login(username, password);
            }
        });
    }
}
//測試
public class DaggerUtils {
   public static final AppModule appModule = spy(new AppModule(RuntimeEnvironment.application));

   public static void setupDagger() {
       AppComponent appComponent = DaggerAppComponent.builder().appModule(appModule).build();
       ComponentHolder.setAppComponent(appComponent);
   }
}
//-------
@RunWith(RobolectricGradleTestRunner.class) //Robolectric相關(guān),看不懂的話忽略
@Config(constants = BuildConfig.class, sdk = 21) //同上
public class LoginActivityTest {

    @Test
    public void testActivityStart() {
        @Test
        public void testActivityStart() {
            DaggerUtils.setupDagger();

            LoginPresenter mockLoginPresenter = mock(LoginPresenter.class);  
            Mockito.when(DaggerUtils.appModule.provideLoginPresenter(any(UserManager.class), any(PasswordValidator.class))).thenReturn(mockLoginPresenter);  

            LoginActivity loginActivity = Robolectric.setupActivity(LoginActivity.class);
            ((EditText) loginActivity.findViewById(R.id.username)).setText("xiaochuang");
            ((EditText) loginActivity.findViewById(R.id.password)).setText("xiaochuang is handsome");
            loginActivity.findViewById(R.id.login).performClick();    
            verify(mockLoginPresenter).login("xiaochuang", "xiaochuang is handsome");  //pass!
        }
    }
}

Robolectric使用

Robolectric就是一個(gè)能夠讓我們在JVM上跑測試時(shí)夠調(diào)用安卓的類的框架

  • android.jar不完整,android相關(guān)方法只是拋出異常RuntimeException("stub!!")
  • 依賴
testCompile 'junit:junit:4.12'
testCompile "org.robolectric:robolectric:3.2.1"```
- api

@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21)
public class MainActivityTest {
@Test
public void testMainActivity() {
MainActivity mainActivity = Robolectric.setupActivity(MainActivity.class);
mainActivity.findViewById(R.id.textView1).performClick();

    Intent expectedIntent = new Intent(mainActivity, SecondActivity.class);  
    ShadowActivity shadowActivity = Shadows.shadowOf(mainActivity);  
    Intent actualIntent = shadowActivity.getNextStartedActivity();  
    Assert.assertEquals(expectedIntent, actualIntent);  
}  

}


- 參考: http://chriszou.com/2016/06/07/android-unit-testing-everything-you-need-to-know.html
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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