摘要:我們寫單元測試,一般都會(huì)用到一個(gè)或多個(gè)單元測試框架,在這里,我們介紹一下這個(gè)測試框架。除了幫我們找出所有的測試方法,并且方便運(yùn)行意外,單元測試框架還幫我們做了其他事情。
我們寫單元測試,一般都會(huì)用到一個(gè)或多個(gè)單元測試框架,在這里,我們介紹一下JUnit4這個(gè)測試框架。這是Java界用的最廣泛,也是最基礎(chǔ)的一個(gè)框架,其他的很多框架,包括我們后面會(huì)看到的Robolectric,都是基于或兼容JUnit4的。
然而首先要解決的問題是。。。
或者換句話說,單元測試框架能夠?yàn)槲覀冏鍪裁茨兀?
從最基本的開始說起,假如我們有這樣一個(gè)類:
public class Calculator { public int add(int one, int another) { // 為了簡單起見,暫不考慮溢出等情況。 return one + another; } public int multiply(int one, int another) { // 為了簡單起見,暫不考慮溢出等情況。 return one * another; } }
如果不用單元測試框架的話,我們要怎么寫測試代碼呢?我們恐怕得寫出下面這樣的代碼:
public class CalculatorTest { public static void main(String[] args) { Calculator calculator = new Calculator(); int sum = calculator.add(1, 2); if(sum == 3) { System.out.println("add() works!") } else { System.out.println("add() does not works!") } int product = calculator.multiply(2, 4); if (product == 8) { System.out.println("multiply() works!") } else { System.out.println("multiply() does not works!") } } }
然后我們再通過某種方式,比如命令行或IDE,運(yùn)行這個(gè)CalculatorTest的main方法,在看著terminal的輸出,才知道測試是通過還是失敗。想想一下,如果我們有很多的類,每個(gè)類都有很多方法,那么就要寫一堆這樣的代碼,每個(gè)類對于一個(gè)含有main方法的test類,同時(shí)main方法里面會(huì)有一堆代碼。這樣既寫起來痛苦,跑起來更痛苦,比如說,你怎么樣一次性跑所有的測試類呢?所以,一個(gè)測試框架為我們做的最基本的事情,就是允許我們按照某種更簡單的方式寫測試代碼,把每一個(gè)測試單元寫在一個(gè)測試方法里面,然后它會(huì)自動(dòng)找出所有的測試方法,并且根據(jù)你的需要,運(yùn)行所有的測試方法,或者是運(yùn)行單個(gè)測試方法,或者是運(yùn)行部分測試方法等等。
對于上面的Calculator例子,如果使用Junit的話,我們可以按照如下的方式寫測試代碼:
public class CalculatorTest { @Test public void testAdd() throws Exception { Calculator calculator = new Calculator(); int sum = calculator.add(1, 2); Assert.assertEquals(3, sum); } @Test public void testMultiply() throws Exception { Calculator calculator = new Calculator(); int product = calculator.multiply(2, 4); Assert.assertEquals(8, product); } }
每一個(gè)被測試的方法(add(), multiply()),寫一個(gè)對應(yīng)的測試方法(testAdd(), testMultiply())。那JUnit怎么知道那些是測試方法,哪些不是呢?這個(gè)是通過前面的@Test注解來標(biāo)志的,只要有這個(gè)注解,JUnit4就會(huì)當(dāng)做是一個(gè)測試方法,方法名其實(shí)是可以隨意起的。當(dāng)然,名字還是應(yīng)該起的更有可讀性一點(diǎn),讓人一看就知道,這個(gè)測試方法是測試了被測的類的那個(gè)方法,或者是測試了那個(gè)功能點(diǎn)等等。
除了幫我們找出所有的測試方法,并且方便運(yùn)行意外,單元測試框架還幫我們做了其他事情。在這個(gè)系列的第一篇文章中我們提到,一個(gè)測試方法主要包括三個(gè)部分:
setup
執(zhí)行操作
驗(yàn)證結(jié)果
而一個(gè)單元測試框架,可以讓我們更方便的寫上面的每一步的代碼,尤其是第一步和第三部。比如說,在上面的CalculatorTest中,testAdd()和testMultiply()都有相同的setup: Calculator calculator = new Calculator();,如果Calculator還有其他的方法的話,這行代碼就得重復(fù)更多次,這種duplication是沒必要的。絕大多數(shù)單元測試框架考慮到了這一點(diǎn),它們知道一個(gè)測試類的很多測試方法可能需要相同的setup,所以為我們提供了便捷方法。對于JUnit4,是通過@Before來實(shí)現(xiàn)的:
public class CalculatorTest { Calculator mCalculator; @Before public void setup() { mCalculator = new Calculator(); } @Test public void testAdd() throws Exception { int sum = mCalculator.add(1, 2); assertEquals(3, sum); //為了簡潔,往往會(huì)static import Assert里面的所有方法。 } @Test public void testMultiply() throws Exception { int product = mCalculator.multiply(2, 4); assertEquals(8, product); } }
如果一個(gè)方法被@Before修飾過了,那么在每個(gè)測試方法調(diào)用之前,這個(gè)方法都會(huì)得到調(diào)用。所以上面的例子中,testAdd()被運(yùn)行之前,setup()會(huì)被調(diào)用一次,把mCalculator實(shí)例化,接著運(yùn)行testAdd();testMultiply()被運(yùn)行之前,setup()又會(huì)被調(diào)用一次,把mCalculator再次實(shí)例化,接著運(yùn)行testMultiply()。如果還有其他的測試方法,則以此類推。
對應(yīng)于@Before的,有一個(gè)@After,作用估計(jì)你也猜得到,那就是每個(gè)測試方法運(yùn)行結(jié)束之后,會(huì)得到運(yùn)行的方法。比如一個(gè)測試文件操作的類,那么在它的測試類中,可能@Before里面需要去打開一個(gè)文件,而每個(gè)測試方法運(yùn)行結(jié)束之后,都需要去close這個(gè)文件。這個(gè)時(shí)候就可以把文件close的操作放在@After里面,讓它自動(dòng)去執(zhí)行。
類似的,還有@BeforeClass和@AfterClass。@BeforeClass的作用是,在跑一個(gè)測試類的所有測試方法之前,會(huì)執(zhí)行一次被@BeforeClass修飾的方法,執(zhí)行完所有測試方法之后,會(huì)執(zhí)行一遍被@AfterClass修飾的方法。這兩個(gè)方法可以用來setup和release一些公共的資源,需要注意的是,被這兩個(gè)annotation修飾的方法必須是靜態(tài)的。
前面講的是單元測試框架對于一個(gè)測試方法的第一步“setup”,為我們做的事情。而對于第三部“驗(yàn)證結(jié)果”,則一般是通過一些assert方法來完成的。JUnit為我們提供的assert方法,多數(shù)都在Assert這個(gè)類里面。最常用的那些如下:
assertEquals(expected, actual)
驗(yàn)證expected的值跟actual是一樣的,如果是一樣的話,測試通過,不然的話,測試失敗。如果傳入的是object,那么這里的對比用的是equals()
assertEquals(expected, actual, tolerance)
這里傳入的expected和actual是float或double類型的,大家知道計(jì)算機(jī)表示浮點(diǎn)型數(shù)據(jù)都有一定的偏差,所以哪怕理論上他們是相等的,但是用計(jì)算機(jī)表示出來則可能不是,所以這里運(yùn)行傳入一個(gè)偏差值。如果兩個(gè)數(shù)的差異在這個(gè)偏差值之內(nèi),則測試通過,否者測試失敗。
assertTrue(boolean condition)
驗(yàn)證contidion的值是true
assertFalse(boolean condition)
驗(yàn)證contidion的值是false
assertNull(Object obj)
驗(yàn)證obj的值是null
assertNotNull(Object obj)
驗(yàn)證obj的值不是null
assertSame(expected, actual)
驗(yàn)證expected和actual是同一個(gè)對象,即指向同一個(gè)對象
assertNotSame(expected, actual)
驗(yàn)證expected和actual不是同一個(gè)對象,即指向不同的對象
fail()
讓測試方法失敗
注意:上面的每一個(gè)方法,都有一個(gè)重載的方法,可以在前面加一個(gè)String類型的參數(shù),表示如果驗(yàn)證失敗的話,將用這個(gè)字符串作為失敗的結(jié)果報(bào)告。
比如:
assertEquals("Current user Id should be 1", 1, currentUser.id());
當(dāng)currentUser.id()的值不是1的時(shí)候,在結(jié)果報(bào)道里面將顯示"Current user Id should be 1",這樣可以讓測試結(jié)果更具有可讀性,更清楚錯(cuò)誤的原因是什么。
比較有意思的是最后一個(gè)方法,fail(),你或許會(huì)好奇,這個(gè)有什么用呢?其實(shí)這個(gè)在很多情況下還是有用的,比如最明顯的一個(gè)作用就是,你可以驗(yàn)證你的測試代碼真的是跑了的。
此外,它還有另外一個(gè)重要作用,那就是驗(yàn)證某個(gè)被測試的方法會(huì)正確的拋出異常,不過這點(diǎn)可以通過下面講到的方法,更方便的做到,所以就不講了。
這部分相對來說還是很好理解的,不做過多解釋。
很多時(shí)候,因?yàn)槟承┰颍ū热缯酱a還沒有實(shí)現(xiàn)等),我們可能想讓JUnit忽略某些方法,讓它在跑所有測試方法的時(shí)候不要跑這個(gè)測試方法。要達(dá)到這個(gè)目的也很簡單,只需要在要被忽略的測試方法前面加上@Ignore就可以了,如下:
public class CalculatorTest { Calculator mCalculator; @Before public void setup() { mCalculator = new Calculator(); } // Omit testAdd() and testMultiply() for brevity @Test @Ignore("not implemented yet") public void testFactorial() { } }驗(yàn)證方法會(huì)拋出某些異常
有的時(shí)候,拋出異常是一個(gè)方法正確工作的一部分。比如一個(gè)除法函數(shù),當(dāng)除數(shù)是0的時(shí)候,它應(yīng)該拋出異常,告訴外界,傳入的被除數(shù)是0,示例代碼如下:
public class Calculator { // Omit testAdd() and testMultiply() for brevity public double divide(double divident, double dividor) { if (dividor == 0) throw new IllegalArgumentException("Dividor cannot be 0"); return divident / dividor; }}
那么如何測試當(dāng)傳入的除數(shù)是0的時(shí)候,這個(gè)方法應(yīng)該拋出IllegalArgumentException異常呢?
在Junit中,可以通過給@Test annotation傳入一個(gè)expected參數(shù)來達(dá)到這個(gè)目的,如下:
public class CalculatorTest { Calculator mCalculator; @Before public void setup() { mCalculator = new Calculator(); } // Omit testAdd() and testMultiply() for brevity @Test(expected = IllegalArgumentException.class) public void test() { mCalculator.divide(4, 0); } }
@Test(expected = IllegalArgumentException.class)表示驗(yàn)證這個(gè)測試方法將拋出IllegalArgumentException異常,如果沒有拋出的話,則測試失敗。
在Android項(xiàng)目里面使用JUnit在Android項(xiàng)目里面使用JUnit是很簡單的,你只需要將JUnit這個(gè)library加到你的dependencies里面。
testCompile "junit:junit:4.12"
如果你通過AndroidStudio創(chuàng)建一個(gè)項(xiàng)目,這個(gè)dependency默認(rèn)是加上了的,所以你甚至這步都可以省略。
此外,你需要把測試代碼放到src/test/java 目錄下面。
接下來關(guān)于怎么樣運(yùn)行測試代碼,怎么樣看結(jié)果,請參考這個(gè)系列的第一篇文章的相關(guān)部分,因?yàn)閳D比較多,這邊就不重復(fù)了。
這里讓大家看一下運(yùn)行的結(jié)果是什么樣子的,其中有一個(gè)失敗的測試用例是故意的。如果你直接在AndroidStudio里面跑上面的測試類CalculatorTest的所有測試方法的話,會(huì)看到如下的結(jié)果:
左邊可以看到所有的測試方法,以及每個(gè)方法跑出來的結(jié)果,綠色表示測試通過的測試方法,黃色的感嘆號(hào)或紅色的表示測試失敗的。第三個(gè)那個(gè)有條紋的球球表示被忽略的測試方法。
如果是通過terminal跑的話,則會(huì)看到如下的測試結(jié)果:
這篇文章的相關(guān)代碼可以在github的這個(gè)project看到。
小結(jié)這篇文字大概簡單介紹了JUnit的使用,相對來說是比較簡單,也是比較容易理解的,希望能幫助到大家。其中Assert部分,可以幫我們驗(yàn)證一個(gè)方法的返回結(jié)果。然而,這些只能幫我們測試有返回值的那些方法。在第一篇文章里面我們講了,一個(gè)類的方法分兩種,一是有返回值的方法,這些可以通過我們今天講的JUnit來做測試。而另外一種沒有返回值的方法,即void方法,則要通過另外一個(gè)框架,Mockito,來驗(yàn)證它的正確性。至于怎么樣驗(yàn)證void方法的正確性,以及Mockito的使用,請關(guān)注下一篇文章。
最后,如果你也對安卓單元測試感興趣的話,歡迎加入我們的交流群:
參考:
http://junit.org/junit4/
http://www.vogella.com/tutorials/JUnit/article.html
作者 小創(chuàng) 更多文章 | Github | 公眾號(hào)
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/65784.html
摘要:單元測試三單元測試框架的使用掘金我們寫單元測試,一般都會(huì)用到一個(gè)或多個(gè)單元測試框架,在這里,我們介紹一下這個(gè)測試框架。 Android 單元測試 (三):JUnit 單元測試框架的使用 - Android - 掘金 我們寫單元測試,一般都會(huì)用到一個(gè)或多個(gè)單元測試框架,在這里,我們介紹一下JUnit4這個(gè)測試框架。這是Java界用的最廣泛,也是最基礎(chǔ)的一個(gè)框架,其他的很多框架,包括我們后...
摘要:寫單元測試時(shí),應(yīng)該把這些依賴隔離,讓每個(gè)單元保持獨(dú)立。以上的各種原因,都會(huì)影響單元測試的結(jié)果。在單元測試的基礎(chǔ)上,將相關(guān)模塊組合成為子系統(tǒng)或系統(tǒng)進(jìn)行測試,稱為集成測試??梢钥吹?,單元測試速度比集成測試,也叫測試要快,并且開發(fā)成本也是最低。 showImg(/img/remote/1460000006811144); 原文鏈接:http://www.jianshu.com/p/bc996...
閱讀 1502·2019-08-30 15:55
閱讀 1205·2019-08-30 15:52
閱讀 1320·2019-08-29 13:53
閱讀 1490·2019-08-29 11:19
閱讀 3015·2019-08-26 13:29
閱讀 551·2019-08-26 11:33
閱讀 2638·2019-08-23 17:20
閱讀 1051·2019-08-23 14:14