一、單元測試的好處
單元測試可以幫助我們驗證程序的邏輯是否正確、可以降低bug修復的成本、更有利於代碼重構等等。所以,我們在寫代碼的時候,盡量保證單元測試的覆蓋率。能力好的可以先寫測試用例,再寫功能代碼(測試先行)。
二、使用JUnit
1、JUnit框架:JUnit是一個托管在Github上的開源項目,是Java程序員使用率最高的測試框架,使用@Test注釋來標識指定測試的方法。
2、怎么在JUnit中進行測試呢(以maven項目為例):首先在pom中引入JUnit的依賴,然后在src/test/java中建立相關測試類,編寫測試用例方法,在方法上添加@Test,並在方法內使用合適的assert進行判斷,運行即可。實例如下:
public class MyTest001 { @Test public void test001(){ Assert.assertEquals(1,1); } }
3、命名約定:
①在/src/test/java中建立與/src/main/java相對應的測試類,以Test為后綴。
②測試名稱應該見名知意,一般使用多should、when、then等單詞。
4、JUnit4中常用的注釋
@Test | 將方法標識為測試方法,還可以有expected和timeout兩個屬性 |
@Test(expected = Exception.class) | 如果方法不拋出指定的異常,則失敗 |
@Test(timeout = 100) | 如果方法花費的時間超過100毫秒,則會失敗 |
@Before | 在測試方法執行之前,先執行被@Before標記的方法,用於准備測試環境,如初始化類等。 |
@After | 在測試方法執行之后執行,用於清理測試環境,如刪除臨時數據等。 |
@BeforeClass | 在所有測試開始之前執行一次,用於執行時間密集型活動,如數據庫連接。被@BeforeClass標記的方法需要是static修飾 |
@AfterClass | 在所有測試完成后執行一次。用於執行清理活動,如斷開與數據庫的連接。被@AfterClass標記的方法需要是static修飾 |
@Ignore | 標記應禁用測試。當底層代碼已更改且測試用例尚未調整時,非常有用。最好描述禁用原因。 |
代碼示例:
public class MyTest001 { @BeforeClass public static void beforeClass(){ System.out.println("MyTest001.beforeClass ..."); } @Before public void before(){ System.out.println("MyTest001.before..."); } @Test public void test001(){ System.out.println("MyTest001.test001..."); Assert.assertEquals(1,1); } @Test @Ignore("測試用例沒有修改...") public void test002(){ System.out.println("MyTest001.test002..."); Assert.assertEquals(1,1); } @After public void after(){ System.out.println("MyTest001.after..."); } @AfterClass public static void afterClass(){ System.out.println("MyTest001.afterClass ..."); } }
運行結果如下:
5、JUnit4斷言
JUnit通過Assert類提供的靜態方法來測試某些條件。這些斷言語句通常是以assert開頭。可以指定錯誤信息、預期結果和實際結果。斷言方法將測試返回的實際值和預期值進行比較。如果比較失敗,將拋出java.lang.AssertionError異常。
常用斷言如下,[message]參數可選:
fail([String message]) | 讓測試方法失敗。 |
assertTrue([String message,] boolean condition) | 檢查布爾條件是否為true。對應方法名稱assertFalse |
assertEquals([String message,] 多種類型 unexpected, |
測試兩個值是否相同。對應方法名稱assertNotEquals。 |
assertEquals([String message,] double/float expected, |
測試float或double值是否匹配。容差是必須相同的小數位數。對應方法名稱assertNotEquals。 |
assertNull([String message,] Object object) | 檢查對象是否為空。對應方法名稱assertNotNull。 |
assertSame([String message,]Object expected, Object actual) | 檢查兩個變量是否引用同一個對象。對應方法名稱assertNotSame |
assertArrayEquals([String message,] 各種類型[] expecteds, |
檢查數組內容是否相同。 |
assertArrayEquals([String message,] double/float[] expecteds, |
檢查double/float數組內容是否相同,指定容差范圍。 |
assertThat([String message, ]T actual, Matcher matcher) |
actual為需要測試的變量,matcher為使用Hamcrest的匹配符來表達變量actual期望值的聲明。(其實內部調用的是org.hamcrest.MatcherAssert.assertThat) |
6、測試類中測試方法的執行順序
測試類中的測試方法,也是可以指定按方法名稱的字典順序執行的,在測試類上添加@FixMethodOrder注解,有三個值可選分別是:MethodSorters.DEFAULT:默認執行順序,由方法名hashcode值來決定,如果hash值大小一致,則按名字的字典順序確定;MethodSorters.NAME_ASCENDING:按方法名稱的字典順序;MethodSorters.JVM:按JVM返回的方法名的順序執行。示例代碼如下:
/** * @FixMethodOrder指定測試方法執行順序 * MethodSorters.DEFAULT:默認執行順序,由方法名hashcode值來決定,如果hash值大小一致,則按名字的字典順序確定。 * MethodSorters.NAME_ASCENDING:按方法名稱的字典順序。 * MethodSorters.JVM:按JVM返回的方法名的順序執行。 */ @FixMethodOrder(MethodSorters.JVM) public class MyTest003 { @Test public void testCThird(){ System.out.println("MyTest003.testCThird"); } @Test public void testAFirst(){ System.out.println("MyTest003.testAFirst"); } @Test public void testBSecond(){ System.out.println("MyTest003.testBSecond"); } }
運行結果:MethodSorters.DEFAULT
MethodSorters.NAME_ASCENDING
MethodSorters.JVM
7、JUnit測試套件
我們可以將幾個測試類組合到一個測試套件中,根據填寫的順序運行測試類。
/** * 使用@Suite.SuiteClasses可以將多個測試類放到一起進行測試,也可以包含其他被@Suite.SuiteClasses標記的類 * 以指定的順序運行套件中的所有測試類。 */ @RunWith(Suite.class) @Suite.SuiteClasses({MyTest001.class,MyTest002.class,MyTest003.class}) public class AllTests { }
8、分類測試
我們還可以定義測試類別,並根據注釋包含或排除它們。
public interface FastTests { /* 類別標記 */ } public interface SlowTests { /* 類別標記 */ } public class A { @Test public void a() { fail(); } /** * 該方法屬於SlowTests類別 */ @Test @Category(SlowTests.class) public void b() { } } /** * 該測試類屬於SlowTests、FastTests兩種類別 */ @Category({SlowTests.class, FastTests.class}) public class B { @Test public void c() { } } /** * 分類別運行測試用例 */ @RunWith(Categories.class) @Categories.IncludeCategory(SlowTests.class) //指定包含的類別 @Categories.ExcludeCategory(FastTests.class) //指定排除的類別 @Suite.SuiteClasses( { A.class, B.class }) // 類別是一種套件 public class SlowTestSuite { // 不打開注釋@Categories.ExcludeCategory,將會運行 A.b and B.c, 不會運行 A.a // 打開注釋@Categories.ExcludeCategory,將會運行 A.b }
8、JUnit參數化測試:https://github.com/Pragmatists/JUnitParams
9、JUnit規則:https://github.com/junit-team/junit4/wiki/Rules