[TOC]
介紹
Junit4 是在 Junit3 基礎(chǔ)上改進(jìn)的單元測試框架
- junit4 不需要繼承 TestCase
- 測試方法命名沒有特定要求缸濒,只要在待測方法前加上@Test即可
- 通過
@befroe
替代@setUp
方法足丢,@After
替代@tearDown
方法;
在一個(gè)測試類中庇配,甚至可以使用多個(gè) @Before 來注釋多個(gè)方法斩跌,這些方法都是在每個(gè)測試之前運(yùn)行
@Before 是在每個(gè)測試方法運(yùn)行前均初始化一次,同理 @After 是在每個(gè)測試方法運(yùn)行完畢后捞慌,均運(yùn)行一次
也就是說耀鸦,經(jīng)過這兩個(gè)注釋的初始化和注銷,可以保證各個(gè)測試方法之間的獨(dú)立性而互不干擾啸澡,它的缺點(diǎn)是效率低
- 新增
@BeforeClass
和@AfterClass
使用這兩個(gè)注釋的方法袖订,在該測試類中,的測試方法之前嗅虏、后各運(yùn)行一次洛姑,而不是按照各個(gè)方法各運(yùn)行一次
對于一些資源消耗大的項(xiàng)目,可以使用這兩個(gè)注釋旋恼,減少 @Before @After 運(yùn)行效率低的問題
- 新增測試類型
異常測試 @Test(expected=*.class)
和超時(shí)測試 @Test(timeout=xxx)
- 新增斷言方法
新的assert方法 assertEquals(Object[] expected, Object[] actual)
吏口,用于比較數(shù)組數(shù)據(jù)
測試用例寫作注意事項(xiàng)
- 測試方法上面必須使用
@Test
注解進(jìn)行修飾 - 測試方法必須使用
public void進(jìn)行修飾奄容,不能帶有任何參數(shù)
- 測試單元中的每一個(gè)方法
必須獨(dú)立測試,每個(gè)測試方法之間不能有依賴
- 新建一個(gè)源代碼目錄用來存放測試代碼
測試類的包應(yīng)該與被測試類的包保持一致
- 測試類使用Test做為類名的后綴(非必要)
- 測試方法使用test作為方法名的前綴(非必要)
- 盡量使用 assert 關(guān)鍵字來斷言錯(cuò)誤(非必要)
測試分析原則
- Failure 一般是單元測試使用的斷言方法判斷失敗引起产徊,說明預(yù)期結(jié)果和程序運(yùn)行結(jié)果不一致
- Error 是有代碼異常引起的昂勒,產(chǎn)生于測試代碼本身中的Bug
- 測試用例
不是用來證明你是對的,而是用來證明你沒有錯(cuò)
測試流程
@BeforeClass
public static void setUpBeforeClass() throws Exception {
}
@AfterClass
public static void setUpAfterClass() throws Exception {
}
@Before
public void before() throws Exception {
}
@After
public void after() throws Exception {
}
@BeforeClass
所修飾的方法在所有方法加載前執(zhí)行舟铜,而且他是靜態(tài)的在類加載后就會(huì)執(zhí)行該方法戈盈,在內(nèi)存中只有一份實(shí)例,適合用來加載配置文件
@AfterClass
所修飾的方法在所有方法執(zhí)行完畢之后執(zhí)行谆刨,通常用來進(jìn)行資源清理塘娶,例如關(guān)閉數(shù)據(jù)庫連接@Before
和@After
在每個(gè)測試方法執(zhí)行前都會(huì)執(zhí)行一次
JUnit注解活用
-
@Test(excepted=XX.class)
在運(yùn)行時(shí)忽略某個(gè)異常 -
@Test(timeout=[ms])
允許程序運(yùn)行的時(shí)間單位毫秒 -
@Ignore
所修飾的方法被測試器忽略 -
@RunWith
可以修改測試運(yùn)行器org.junit.runner.Runne
單元測試的執(zhí)行順序
默認(rèn)情況下,單元測試是無序的痊夭,單元測試框架的一個(gè)出發(fā)點(diǎn)是
單元性
刁岸,即每個(gè)單元之間互不影響,因此設(shè)置單元測試的執(zhí)行順序是沒有意義的
不過如果涉及到特殊流程她我,或者非要讓測試的執(zhí)行順序在自己的控制之下虹曙,也是可以做到的
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class TempTest {
}
@FixMethodOrder
Junit 4.11 里增的指定測試方法執(zhí)行順序的特性,支持順序有三種番舆,這里是按名字排序
默認(rèn)(MethodSorters.DEFAULT)
默認(rèn)順序由方法名hashcode值來決定酝碳,如果hash值大小一致,則按名字的字典順序確定恨狈,由于hashcode的生成和操作系統(tǒng)相關(guān)疏哗,對于不同操作系統(tǒng),可能會(huì)出現(xiàn)不一樣的執(zhí)行順序禾怠,在某一操作系統(tǒng)上返奉,多次執(zhí)行的順序不變
排序方法
/**
* DEFAULT sort order
* @type {Comparator}
*/
public static Comparator<Method> DEFAULT = new Comparator<Method>() {
public int compare(Method m1, Method m2) {
int i1 = m1.getName().hashCode();
int i2 = m2.getName().hashCode();
if (i1 != i2) {
return i1 < i2 ? -1 : 1;
}
return NAME_ASCENDING.compare(m1, m2);
}
};
按方法名(MethodSorters.NAME_ASCENDING)
按方法名稱的進(jìn)行排序,由于是按字符的字典順序刃宵,所以以這種方式指定執(zhí)行順序會(huì)始終保持一致
需要對測試方法有一定的命名規(guī)則衡瓶,如 測試方法均以 test_nnn 開頭, nnn 代表 000-999 的數(shù)字
排序方法
/**
* Method name ascending lexicographic sort order, with {@link Method#toString()} as a tiebreaker
* @type {Comparator}
*/
public static Comparator<Method> NAME_ASCENDING = new Comparator<Method>() {
public int compare(Method m1, Method m2) {
final int comparison = m1.getName().compareTo(m2.getName());
if (comparison != 0) {
return comparison;
}
return m1.toString().compareTo(m2.toString());
}
};
JVM(MethodSorters.JVM)
按JVM返回的方法名的順序執(zhí)行,此種方式下測試方法的執(zhí)行順序是不可預(yù)測的牲证,即每次運(yùn)行的順序可能都不一樣
注意哮针,JVM 返回的方法名在不同 jdk 上表現(xiàn)可能不一致,不過在 1.7 及以后的 jdk 返回的順序肯定不一樣
Junit4 測試方法順序原理
實(shí)際上 Junit里是通過反射機(jī)制得到某個(gè)Junit里的所有測試方法坦袍,并生成一個(gè)方法的數(shù)組十厢,然后依次執(zhí)行數(shù)組里的這些測試方法;
而當(dāng)用annotation指定了執(zhí)行順序捂齐,Junit在得到測試方法的數(shù)組后蛮放,會(huì)根據(jù)指定的順序?qū)?shù)組里的方法進(jìn)行排序
public static Method[] getDeclaredMethods(Class<?> clazz) {
Comparator<Method> comparator = getSorter(clazz.getAnnotation(FixMethodOrder.class)); //獲取測試類指定的執(zhí)行順序
Method[] methods = clazz.getDeclaredMethods();
if (comparator != null) {
Arrays.sort(methods, comparator); //根據(jù)指定順序排序
}
return methods;
}
定義的順序?yàn)?/p>
package org.junit.runners;
/**
* Sort the methods into a specified execution order.
* Defines common {@link MethodSorter} implementations.
*
* @since 4.11
*/
public enum MethodSorters {
/**
* Sorts the test methods by the method name, in lexicographic order,
* with {@link Method#toString()} used as a tiebreaker
*/
NAME_ASCENDING(MethodSorter.NAME_ASCENDING),
/**
* Leaves the test methods in the order returned by the JVM.
* Note that the order from the JVM may vary from run to run
*/
JVM(null),
/**
* Sorts the test methods in a deterministic, but not predictable, order
*/
DEFAULT(MethodSorter.DEFAULT);
}
當(dāng)設(shè)置為MethodSorters.JVM時(shí),其并沒有提供一個(gè)Comparator的實(shí)現(xiàn)
所以執(zhí)行方法的順序?qū)嶋H上就是 clazz.getDeclaredMethods();
得到的數(shù)組里方法的順序
而由于java里對getDeclaredMethods返回的方法沒有指定任何順序奠宜,所以最終導(dǎo)致Junit測試方法的執(zhí)行順序也不是確定的
測試套件
測試套件是組織測試類一起運(yùn)行的測試類
@RunWith(Suite.class)
@Suite.SuiteClasses({UserTest1,UserTest2,UserTest3})
public class SuiteTest{
}
測試套件注意事項(xiàng)
作為測試套件的入口類包颁,類中不能包含任何方法
- 更改測試運(yùn)行器Suite.class
- 將需要運(yùn)行的測試類放入Suite.SuiteClasses({})的數(shù)組中
參數(shù)化設(shè)置
需要測試的僅僅是測試數(shù)據(jù)瞻想,代碼結(jié)構(gòu)是不變的,只需要更改測試入?yún)?/p>
@RunWith(Parameterized.class)
public class ParameterTest {
int expected = 0;
int input1 = 0;
int input2 = 0;
@Parameters
public static Collection<Object[]> t() {
return Arrays.asList(new Object[][]{
{3,1,2},
{5,2,3}
});
}
public ParameterTest(int expected,int input1,int input2) {
this.expected = expected;
this.input1 = input1;
this.input2 = input2;
}
@Test
public void testAdd() {
assertEquals(expected, UserDao.add(input1,input2));
}
}
代碼流程為
- 更改默認(rèn)的測試運(yùn)行器為
@RunWith(Parameterized.class)
- 聲明變量來存放
預(yù)期值和測試值
例子中為 expected input1 input2 - 聲明一個(gè)返回值為
Collection的公共靜態(tài)方法娩嚼,并用@Parameters修飾
- 為測試類聲明一個(gè)帶有參數(shù)的公共構(gòu)造函數(shù)
ParameterTest
蘑险,并在其中為他聲明變量賦值
單元測試統(tǒng)計(jì)與評測
單元測試代碼覆蓋介紹
首先,代碼覆蓋屬于單元測試岳悟,集成測試不計(jì)算在其中
需要理解被測程序的邏輯佃迄,需要考慮到每個(gè)函數(shù)的輸入與輸出,邏輯分支代碼的執(zhí)行情況贵少,這個(gè)時(shí)候我們的測試執(zhí)行情況就以代碼覆蓋率來衡量呵俏,可以理解為白盒覆蓋
這里介紹 java 代碼的覆蓋分類
- 行覆蓋率:度量被測程序的每行代碼是否被執(zhí)行,判斷標(biāo)準(zhǔn)行中是否至少有一個(gè)指令被執(zhí)行
- 類覆蓋率:度量計(jì)算class類文件是否被執(zhí)行
- 分支覆蓋率:度量if和switch語句的分支覆蓋情況滔灶,計(jì)算一個(gè)方法里面的總分支數(shù)普碎,確定執(zhí)行和不執(zhí)行的 分支數(shù)量
- 方法覆蓋率:度量被測程序的方法執(zhí)行情況,是否執(zhí)行取決于方法中是否有至少一個(gè)指令被執(zhí)行
- 指令覆蓋:計(jì)數(shù)單元是單個(gè)java二進(jìn)制代碼指令录平,指令覆蓋率提供了代碼是否被執(zhí)行的信息随常,度量完全 獨(dú)立源碼格式
- 圈復(fù)雜度:在(線性)組合中,計(jì)算在一個(gè)方法里面所有可能路徑的最小數(shù)目萄涯,缺失的復(fù)雜度同樣表示測試案例沒有完全覆蓋到這個(gè)模塊
java 代碼覆蓋測試方法
代碼生成二進(jìn)制時(shí)使用的ASM技術(shù)修改字節(jié)碼方法,可以修改Jar文件唆鸡、class文件字節(jié)碼文件涝影,來注入源碼,注入的過程做插樁争占,然后執(zhí)行測試用例燃逻,統(tǒng)計(jì)是否調(diào)用
目前 java 使用的UAT和自動(dòng)統(tǒng)計(jì)覆蓋測試的技術(shù)
- maven 自動(dòng)化構(gòu)建鏈 http://maven.apache.org/ 其中 http://maven.apache.org/plugins/index.html 有 Junit 增強(qiáng)插件
- gradle 自動(dòng)化構(gòu)建鏈 https://gradle.org/ 其中 https://plugins.gradle.org/search?term=junit 有增強(qiáng)插件
- mockito 注入式單元測試 http://site.mockito.org/
- jacoco 覆蓋率統(tǒng)計(jì) http://www.eclemma.org/jacoco/