什么是單元測試
單元測試是由一組獨(dú)立的測試構(gòu)成,每個(gè)測試針對(duì)軟件中的一個(gè)單獨(dú)的程序單元。單元測試并非檢查程序單元直接是否能夠合作良好,而是檢查單個(gè)程序單元的行為是否正確。
事實(shí)上,單元測試是一種驗(yàn)證行為,測試和驗(yàn)證程序中的每一項(xiàng)的正確性。
為什么要進(jìn)行單元測試
對(duì)于單元測試,人們往往存在很多誤解:
- 浪費(fèi)時(shí)間太多,本身項(xiàng)目的時(shí)間就很緊張,沒有時(shí)間去寫單元測試。
- 過度的依賴測試人員,認(rèn)為軟件開發(fā)人員不應(yīng)該參與單元測試。
- 認(rèn)為單元測試不必要,代碼寫得很好了,no bug,no warning。
- 老代碼結(jié)構(gòu)混亂,耦合度高,為了寫單元測試修改代碼結(jié)構(gòu),意義不大,投入跟產(chǎn)出不成比例。
單元測試真的這么雞肋么?No,No,No?。?!
試想
測試人員給你報(bào)了一個(gè)bug,但是由于之后的merge失誤導(dǎo)致代碼丟失,或者別人修改代碼導(dǎo)致這個(gè)bug再次復(fù)現(xiàn)。
重構(gòu)代碼的時(shí)候,被bug淹沒。造成你持續(xù)不斷的改bug,持續(xù)不斷的加班。
-
明明很正常的功能,怎么現(xiàn)在突然不能用了?是接口的問題,還是有人修改了這個(gè)功能的邏輯?
。。。
如果你也經(jīng)常遇到這些困惑,那么你就需要對(duì)項(xiàng)目進(jìn)行單元測試了。
因?yàn)?strong>單元測試具有以下優(yōu)勢(shì):
-
幫助理解需求
單元測試應(yīng)該反映Use Case,把被測單元當(dāng)成黑盒測試其外部行為。
-
提高實(shí)現(xiàn)質(zhì)量
單元測試不保證程序做正確的事,但能幫助保證程序正確地做事,從而提高實(shí)現(xiàn)質(zhì)量。
-
測試成本低
相比集成測試、驗(yàn)收測試,單元測試所依賴的外部環(huán)境少,自動(dòng)化程度高,時(shí)間短,節(jié)約了測試成本。
-
反饋速度快
單元測試提供快速反饋,把bug消滅在開發(fā)階段,減少問題流到集成測試、驗(yàn)收測試和用戶,降低了軟件質(zhì)量控制的成本。
-
利于重構(gòu)
由于有單元測試作為回歸測試用例,有助于預(yù)防在重構(gòu)過程中引入bug。
-
文檔作用
單元測試提供了被測單元的使用場景,起到了使用文檔的作用。
-
對(duì)設(shè)計(jì)的反饋
一個(gè)模塊很難進(jìn)行單元測試通常是不良設(shè)計(jì)的信號(hào),單元測試可以反過來指導(dǎo)設(shè)計(jì)出高內(nèi)聚、低耦合的模塊。
怎么進(jìn)行單元測試
Android 單元測試分類
Android 單元測試分為兩大類:
app/src
├── androidTestjava (Instrumented 單元測試、UI測試)
├── main/java (業(yè)務(wù)代碼)
└── test/java (Local 單元測試)
-
Local test:
運(yùn)行在本地的JVM虛擬機(jī)上,不依賴Android框架。
-
Instrumented tests:
通過Android系統(tǒng)的Instrumented測試框架,運(yùn)行測試代碼在真實(shí)手機(jī)上。
Android Junit + Mockito + Powermock單元測試方案
Junit + Mockito + Powermock簡介
Junit 是一個(gè)Java語言的單元測試框架
Mockito 是一個(gè)Mock框架,我們可以通過Mockito框架創(chuàng)建配置mock對(duì)象。
Powermock 可以針對(duì)static,final,private方法進(jìn)行mock
Junit + Mockito + Powermock使用
強(qiáng)烈建議你熟讀以下內(nèi)容,來熟悉Junit + Mockito + Powermock的使用。
- Mockito 中文文檔 ( 2.0.26 beta )
- Mockito reference documentation
- powermock wiki
- Unit tests with Mockito - Tutorial
比如說我們要對(duì)Calculate類進(jìn)行單元測試
public class Calculate {
private int mPrivate;
private final int mPrivateFinal = 0;
private static int mPrivateStatic = 0;
private static final int mPrivateStaticFinal = 0;
public int mPublic;
public final int mPublicFinal = 0;
public static int mPublicStatic = 0;
public static final int mPublicStaticFinal = 0;
public void voidPublicMethod(int a, int b) {
return;
}
public int addPublicMethod(int a, int b) {
return a + b;
}
private int addPrivateMethod(int a, int b) {
return a + b;
}
public static int addPublicStaticMethod(int a, int b) {
return a + b;
}
private static int addPrivateStaticMethod(int a, int b) {
return a + b;
}
}
-
測試Public 變量
@Test public void testPublicField() { assertEquals(mCalculate.mPublic, 0); assertEquals(mCalculate.mPublicFinal, 0); assertEquals(Calculate.mPublicStatic, 0); assertEquals(Calculate.mPublicStaticFinal, 0); mCalculate.mPublic = 1; Calculate.mPublicStatic = 2; assertEquals(mCalculate.mPublic, 1); assertEquals(mCalculate.mPublicFinal, 0); assertEquals(Calculate.mPublicStatic, 2); } -
測試Public 方法
@Test public void testAddPublicMethod() { //when when(mCalculate.addPublicMethod(anyInt(), anyInt())) .thenReturn(0) .thenReturn(1); //call method for (int i = 0; i < 2; i++) { //verify assertEquals(mCalculate.addPublicMethod(i, i), i); } //verify verify(mCalculate, times(2)).addPublicMethod(anyInt(), anyInt()); verify(mCalculate, atLeast(1)).addPublicMethod(anyInt(), anyInt()); verify(mCalculate, atLeastOnce()).addPublicMethod(anyInt(), anyInt()); verify(mCalculate, atMost(2)).addPublicMethod(anyInt(), anyInt()); } -
測試Public 返回Void 方法
@Test public void testAddPublicVoidMethod() { //when doNothing().when(mCalculate).voidPublicMethod(anyInt(), anyInt()); } -
測試Public Static 方法
@Test public void testAddPublicStaicMethod() throws Exception { PowerMockito.mockStatic(Calculate.class); PowerMockito.when(Calculate.class, "addPublicStaticMethod", anyInt(), anyInt()) .thenReturn(0) .thenReturn(1); } -
測試Private Static 變量
@Test public void testPrivate() throws IllegalAccessException { PowerMockito.mockStatic(Calculate.class); assertEquals(Whitebox.getField(Calculate.class, "mPrivate").getInt(mCalculate), 0); assertEquals(Whitebox.getField(Calculate.class, "mPrivateFinal").getInt(mCalculate), 0); assertEquals(Whitebox.getField(Calculate.class, "mPrivateStatic").getInt(null), 0); assertEquals(Whitebox.getField(Calculate.class, "mPrivateStaticFinal").getInt(null), 0); Whitebox.setInternalState(mCalculate, "mPrivate", 1); Whitebox.setInternalState(Calculate.class, "mPrivateStatic", 1, Calculate.class); assertEquals(Whitebox.getField(Calculate.class, "mPrivate").getInt(mCalculate), 1); assertEquals(Whitebox.getField(Calculate.class, "mPrivateFinal").getInt(mCalculate), 0); assertEquals(Whitebox.getField(Calculate.class, "mPrivateStatic").getInt(null), 1); assertEquals(Whitebox.getField(Calculate.class, "mPrivateStaticFinal").getInt(null), 0); } -
測試Private 方法
@Test public void testAddPrivateMethod() throws Exception { PowerMockito.mockStatic(Calc.class); //when PowerMockito.when(mCalculate,"addPrivateMethod",anyInt(),anyInt()) .thenReturn(0) .thenReturn(1); } -
測試Private static 方法
@Test public void testAddPrivateStaicMethod() throws Exception { PowerMockito.mockStatic(Calculate.class); PowerMockito.when(Calculate.class, "addPrivateStaticMethod", anyInt(), anyInt()) .thenReturn(0) .thenReturn(1); }
對(duì)單例進(jìn)行mock
比如對(duì)以下代碼進(jìn)行mock
public class Singleton {
public String PUBLIC_STR = "public_str";
private static class ServiceSingleton {
private static final Singleton SINGLE = new Singleton();
}
public static Singleton get() {
return ServiceSingleton.SINGLE;
}
public String getPublicStr() {
return PUBLIC_STR;
}
}
public class SingletonUse {
private static final String PUBLIC_STR = "public_str";
private static class ServiceSingleton {
private static final SingletonUse SINGLE = new SingletonUse();
}
public static SingletonUse get() {
return ServiceSingleton.SINGLE;
}
public String getUsePublicStr() {
return Singleton.get().getPublicStr();
}
}
測試類
@RunWith(PowerMockRunner.class)
@PrepareForTest({Singleton.class, SingletonUse.class})
public class SingletonUseTest {
private SingletonUse singletonUse;
@Before
public void setUp() throws Exception {
Singleton singleton = PowerMockito.mock(Singleton.class);
PowerMockito.mockStatic(Singleton.class);
PowerMockito.doReturn(singleton).when(Singleton.class, "get");
singletonUse = PowerMockito.mock(SingletonUse.class);
PowerMockito.mockStatic(SingletonUse.class);
PowerMockito.doReturn(singletonUse).when(SingletonUse.class, "get");
}
@Test
public void getUsePublicStr() throws Exception {
PowerMockito.doReturn("123456").when(singletonUse,"getUsePublicStr");
}
}