您好,登錄后才能下訂單哦!
前言
單元測試是編寫測試代碼,用來檢測特定的、明確的、細顆粒的功能。單元測試并不一定保證程序功能是正確的,更不保證整體業務是準備的。
單元測試不僅僅用來保證當前代碼的正確性,更重要的是用來保證代碼修復、改進或重構之后的正確性。
一般來說,單元測試任務包括
JUNIT
JUnit是Java單元測試框架,已經在Eclipse中默認安裝。目前主流的有JUnit3和JUnit4。JUnit3中,測試用例需要繼承TestCase類。JUnit4中,測試用例無需繼承TestCase類,只需要使用@Test等注解。
Junit3
先看一個Junit3的樣例
// 測試java.lang.Math // 必須繼承TestCase public class Junit3TestCase extends TestCase { public Junit3TestCase() { super(); } // 傳入測試用例名稱 public Junit3TestCase(String name) { super(name); } // 在每個Test運行之前運行 @Override protected void setUp() throws Exception { System.out.println("Set up"); } // 測試方法。 // 方法名稱必須以test開頭,沒有參數,無返回值,是公開的,可以拋出異常 // 也即類似public void testXXX() throws Exception {} public void testMathPow() { System.out.println("Test Math.pow"); Assert.assertEquals(4.0, Math.pow(2.0, 2.0)); } public void testMathMin() { System.out.println("Test Math.min"); Assert.assertEquals(2.0, Math.min(2.0, 4.0)); } // 在每個Test運行之后運行 @Override protected void tearDown() throws Exception { System.out.println("Tear down"); } }
如果采用默認的TestSuite,則測試方法必須是public void testXXX() [throws Exception] {}的形式,并且不能存在依賴關系,因為測試方法的調用順序是不可預知的。
上例執行后,控制臺會輸出
Set up Test Math.pow Tear down Set up Test Math.min Tear down
從中,可以猜測到,對于每個測試方法,調用的形式是:
testCase.setUp(); testCase.testXXX(); testCase.tearDown();
運行測試方法
在Eclipse中,可以直接在類名或測試方法上右擊,在彈出的右擊菜單中選擇Run As -> JUnit Test。
在Mvn中,可以直接通過mvn test命令運行測試用例。
也可以通過Java方式調用,創建一個TestCase實例,然后重載runTest()方法,在其方法內調用測試方法(可以多個)。
TestCase test = new Junit3TestCase("mathPow") { // 重載 protected void runTest() throws Throwable { testMathPow(); }; }; test.run();
更加便捷地,可以在創建TestCase實例時直接傳入測試方法名稱,JUnit會自動調用此測試方法,如
TestCase test = new Junit3TestCase("testMathPow"); test.run();
Junit TestSuite
TestSuite是測試用例套件,能夠運行過個測試方法。如果不指定TestSuite,會創建一個默認的TestSuite。默認TestSuite會掃描當前內中的所有測試方法,然后運行。
如果不想采用默認的TestSuite,則可以自定義TestSuite。在TestCase中,可以通過靜態方法suite()返回自定義的suite。
import junit.framework.Assert; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; public class Junit3TestCase extends TestCase { //... public static Test suite() { System.out.println("create suite"); TestSuite suite = new TestSuite(); suite.addTest(new Junit3TestCase("testMathPow")); return suite; } }
允許上述方法,控制臺輸出
寫道 create suite Set up Test Math.pow Tear down
并且只運行了testMathPow測試方法,而沒有運行testMathMin測試方法。通過顯式指定測試方法,可以控制測試執行的順序。
也可以通過Java的方式創建TestSuite,然后調用TestCase,如
// 先創建TestSuite,再添加測試方法 TestSuite testSuite = new TestSuite(); testSuite.addTest(new Junit3TestCase("testMathPow")); // 或者 傳入Class,TestSuite會掃描其中的測試方法。 TestSuite testSuite = new TestSuite(Junit3TestCase.class,Junit3TestCase2.class,Junit3TestCase3.class); // 運行testSuite TestResult testResult = new TestResult(); testSuite.run(testResult);
testResult中保存了很多測試數據,包括運行測試方法數目(runCount)等。
JUnit4
與JUnit3不同,JUnit4通過注解的方式來識別測試方法。目前支持的主要注解有:
下面舉一個樣例:
import org.junit.After; import org.junit.AfterClass; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; public class Junit4TestCase { @BeforeClass public static void setUpBeforeClass() { System.out.println("Set up before class"); } @Before public void setUp() throws Exception { System.out.println("Set up"); } @Test public void testMathPow() { System.out.println("Test Math.pow"); Assert.assertEquals(4.0, Math.pow(2.0, 2.0), 0.0); } @Test public void testMathMin() { System.out.println("Test Math.min"); Assert.assertEquals(2.0, Math.min(2.0, 4.0), 0.0); } // 期望此方法拋出NullPointerException異常 @Test(expected = NullPointerException.class) public void testException() { System.out.println("Test exception"); Object obj = null; obj.toString(); } // 忽略此測試方法 @Ignore @Test public void testMathMax() { Assert.fail("沒有實現"); } // 使用“假設”來忽略測試方法 @Test public void testAssume(){ System.out.println("Test assume"); // 當假設失敗時,則會停止運行,但這并不會意味測試方法失敗。 Assume.assumeTrue(false); Assert.fail("沒有實現"); } @After public void tearDown() throws Exception { System.out.println("Tear down"); } @AfterClass public static void tearDownAfterClass() { System.out.println("Tear down After class"); } }
如果細心的話,會發現Junit3的package是junit.framework,而Junit4是org.junit。
執行此用例后,控制臺會輸出
寫道 Set up before class Set up Test Math.pow Tear down Set up Test Math.min Tear down Set up Test exception Tear down Set up Test assume Tear down Tear down After class
可以看到,執行次序是@BeforeClass -> @Before -> @Test -> @After -> @Before -> @Test -> @After -> @AfterClass。@Ignore會被忽略。
運行測試方法
與Junit3類似,可以在Eclipse中運行,也可以通過mvn test命令運行。
Assert
Junit3和Junit4都提供了一個Assert類(雖然package不同,但是大致差不多)。Assert類中定義了很多靜態方法來進行斷言。列表如下:
Mock/Stub
Mock和Stub是兩種測試代碼功能的方法。Mock測重于對功能的模擬。Stub測重于對功能的測試重現。比如對于List接口,Mock會直接對List進行模擬,而Stub會新建一個實現了List的TestList,在其中編寫測試的代碼。
強烈建議優先選擇Mock方式,因為Mock方式下,模擬代碼與測試代碼放在一起,易讀性好,而且擴展性、靈活性都比Stub好。
比較流行的Mock有:
JMock
EasyMock
Mockito
powermock
其中EasyMock和Mockito對于Java接口使用接口代理的方式來模擬,對于Java類使用繼承的方式來模擬(也即會創建一個新的Class類)。Mockito支持spy方式,可以對實例進行模擬。但它們都不能對靜態方法和final類進行模擬,powermock通過修改字節碼來支持了此功能。
EasyMock
EasyMock把測試過程分為三步:錄制、運行測試代碼、驗證期望。
錄制過程大概就是:期望method(params)執行times次(默認一次),返回result(可選),拋出exception異常(可選)。
驗證期望過程將會檢查方法的調用次數。
一個簡單的樣例是:
@Test public void testListInEasyMock() { List list = EasyMock.createMock(List.class); // 錄制過程 // 期望方法list.set(0,1)執行2次,返回null,不拋出異常 expect1: EasyMock.expect(list.set(0, 1)).andReturn(null).times(2); // 期望方法list.set(0,1)執行1次,返回null,不拋出異常 expect2: EasyMock.expect(list.set(0, 1)).andReturn(1); // 執行測試代碼 EasyMock.replay(list); // 執行list.set(0,1),匹配expect1期望,會返回null Assert.assertNull(list.set(0, 1)); // 執行list.set(0,1),匹配expect1(因為expect1期望執行此方法2次),會返回null Assert.assertNull(list.set(0, 1)); // 執行list.set(0,1),匹配expect2,會返回1 Assert.assertEquals(1, list.set(0, 1)); // 驗證期望 EasyMock.verify(list); }
EasyMock還支持嚴格的檢查,要求執行的方法次序與期望的完全一致。
Mockito
Mockito是Google Code上的一個開源項目,Api相對于EasyMock更好友好。與EasyMock不同的是,Mockito沒有錄制過程,只需要在“運行測試代碼”之前對接口進行Stub,也即設置方法的返回值或拋出的異常,然后直接運行測試代碼,運行期間調用Mock的方法,會返回預先設置的返回值或拋出異常,最后再對測試代碼進行驗證。
官方提供了很多樣例,基本上包括了所有功能,可以去看看。
這里從官方樣例中摘錄幾個典型的:
驗證調用行為
import static org.mockito.Mockito.*; //創建Mock List mockedList = mock(List.class); //使用Mock對象 mockedList.add("one"); mockedList.clear(); //驗證行為 verify(mockedList).add("one"); verify(mockedList).clear();
對Mock對象進行Stub
//也可以Mock具體的類,而不僅僅是接口 LinkedList mockedList = mock(LinkedList.class); //Stub when(mockedList.get(0)).thenReturn("first"); // 設置返回值 when(mockedList.get(1)).thenThrow(new RuntimeException()); // 拋出異常 //第一個會打印 "first" System.out.println(mockedList.get(0)); //接下來會拋出runtime異常 System.out.println(mockedList.get(1)); //接下來會打印"null",這是因為沒有stub get(999) System.out.println(mockedList.get(999)); // 可以選擇性地驗證行為,比如只關心是否調用過get(0),而不關心是否調用過get(1) verify(mockedList).get(0);
代碼覆蓋率
比較流行的工具是Emma和Jacoco,Ecliplse插件有eclemma。eclemma2.0之前采用的是Emma,之后采用的是Jacoco。這里主要介紹一下Jacoco。Eclmama由于是Eclipse插件,所以非常易用,就不多做介紹了。
Jacoco
Jacoco可以嵌入到Ant、Maven中,也可以使用Java Agent技術監控任意Java程序,也可以使用Java Api來定制功能。
Jacoco會監控JVM中的調用,生成監控結果(默認保存在jacoco.exec文件中),然后分析此結果,配合源代碼生成覆蓋率報告。
需要注意的是:監控和分析這兩步,必須使用相同的Class文件,否則由于Class不同,而無法定位到具體的方法,導致覆蓋率均為0%。
Java Agent嵌入
首先,需要下載jacocoagent.jar文件,然后在Java程序啟動參數后面加上 -javaagent:[yourpath/]jacocoagent.jar=[option1]=[value1],[option2]=[value2],具體的options可以在此頁面找到。默認會在JVM關閉時(注意不能是kill -9),輸出監控結果到jacoco.exec文件中,也可以通過socket來實時地輸出監控報告(可以在Example代碼中找到簡單實現)。
Java Report
可以使用Ant、Mvn或Eclipse來分析jacoco.exec文件,也可以通過API來分析。
public void createReport() throws Exception { // 讀取監控結果 final FileInputStream fis = new FileInputStream(new File("jacoco.exec")); final ExecutionDataReader executionDataReader = new ExecutionDataReader(fis); // 執行數據信息 ExecutionDataStore executionDataStore = new ExecutionDataStore(); // 會話信息 SessionInfoStore sessionInfoStore = new SessionInfoStore(); executionDataReader.setExecutionDataVisitor(executionDataStore); executionDataReader.setSessionInfoVisitor(sessionInfoStore); while (executionDataReader.read()) { } fis.close(); // 分析結構 final CoverageBuilder coverageBuilder = new CoverageBuilder(); final Analyzer analyzer = new Analyzer(executionDataStore, coverageBuilder); // 傳入監控時的Class文件目錄,注意必須與監控時的一樣 File classesDirectory = new File("classes"); analyzer.analyzeAll(classesDirectory); IBundleCoverage bundleCoverage = coverageBuilder.getBundle("Title"); // 輸出報告 File reportDirectory = new File("report"); // 報告所在的目錄 final HTMLFormatter htmlFormatter = new HTMLFormatter(); // HTML格式 final IReportVisitor visitor = htmlFormatter.createVisitor(new FileMultiReportOutput(reportDirectory)); // 必須先調用visitInfo visitor.visitInfo(sessionInfoStore.getInfos(), executionDataStore.getContents()); File sourceDirectory = new File("src"); // 源代碼目錄 // 遍歷所有的源代碼 // 如果不執行此過程,則在報告中只能看到方法名,但是無法查看具體的覆蓋(因為沒有源代碼頁面) visitor.visitBundle(bundleCoverage, new DirectorySourceFileLocator(sourceDirectory, "utf-8", 4)); // 執行完畢 visitor.visitEnd(); }
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。