JUnit單元測試教程(翻譯自Java Code Geeks)
說明
本教程翻譯自Java Code Geeks,原文網址:https://www.javacodegeeks.com/2014/11/junit-tutorial-unit-testing.html#suite_tests。翻譯的過程中,有少部分內容是譯者新增的解釋說明和對原文章中錯誤地方的修正。水平有限,如果有錯誤的地方,希望能在我的部落格裡告訴我,謝謝。
相關的例子不想自己寫的同學們可以去這裡下載:http://download.csdn.net/detail/rainnnbow/9603847
1. 單元測試簡介
1.1 什麼是單元測試?
一個單元可以是一個方法、一個類、一個包、 或者一個子系統。所以術語單元測試表示對程式碼中的很小的單元的測試行為,以確保它們能夠按預期工作。例如,我們給定一些輸入,測試是否是預期的輸出,或者測試一個條件是真還是假。
這種測試行為能幫助開發者發現隱藏在程式碼後面的錯誤,並提高程式碼質量。單元測試也被用來確保程式碼能夠按照預期工作,以防止未來可能的變化。
1.2 測試覆蓋
總的來說,開發者社群對程式碼測試要達到多少比例存在不同的觀點。許多開發者認為程式碼的測試覆蓋率要達到100%。另一些則認為50%或者更低就可以了。但無論如何,你都應該為你的程式碼中複雜的以及重要的部分編寫測試。
1.3 Java中的單元測試
Java中最著名的測試框架是JUnit,本指南是專門介紹JUnit的,接下來的部分將詳細介紹該測試框架的更多細節。Java中另一個著名的測試框架是TestNG。
2. JUnit簡介
JUnit是一個開源的測試框架,它被用來編寫和執行可重複的自動化測試,保證我們的程式碼按照預期的執行。JUnit被廣泛使用:它可以在Java程式中單獨使用(使用命令列)或者結合IDE使用,如Eclipse。
JUnit提供以下功能:
- 斷言測試的預期結果。
- 共享測試資料的特性。
- 使用Test suites簡化測試用例的組織和執行。
- 圖形化和文字化測試用例?(Graphical and textual test runners.)
JUnit可以用來測試以下內容:
- 一個物件
- 部分物件——一個方法或幾個相互影響的方法
- 多個物件之間的互動
2.1 使用Eclipse實現簡單JUnit測試例子
在這一節我們將展示一個簡單的JUnit例子。首先來看一下我們要測試的類:
Calculate.java:
package com.javacodegeeks.junit;
public class Calculate {
public int sum(int var1, int var2) { System.out.println("Adding values: " + var1 + " + " + var2); return var1 + var2; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
在上面的原始碼中,我們看到該類有一個public的方法sum(),方法接受兩個int引數,相加之後返回結果。我們將測試這個方法。因此我們需要建立另一個類,並在這個類中為測試Calculate類中的所有需要測試的方法一一編寫測試方法(在這個例子中,我們只有一個方法需要測試)。這是最普通的使用方法。當然如果一個方法非常複雜,我們可以為這個方法編寫多個測試方法。編寫測試用例的更多細節,我們將在下一節介紹。下面的CalculateTest.java類就是我們的測試類:
package com.javacodegeeks.junit;
import static org.junit.Assert.*;
import org.junit.Test;
public class CalculateTest { Calculate calculation = new Calculate(); int sum = calculation.sum(2, 5); int testSum = 7; @Test public void testSum() { System.out.println("@Test sum(): " + sum + " = " + testSum); assertEquals(sum, testSum); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
讓我們來解釋一下上面的程式碼。首先我們看到在testSum()方法上有一個@Test註解。這個註解表示這個方法可以作為一個測試用例執行。所以testSum()方法被用來測試sum()方法。我們還看到了assertEquals(sum, testSum)方法,assertEquals ([String message], object expected, object actual)方法用來接手兩個物件,並斷言這兩個物件是否相等。
執行該測試類,Run As–>JUnit Test,程式輸出將如下所示:
為了檢視JUnit測試的實際結果,Eclipse提供了JUnit視窗來顯示測試結果。在這個用例中,測試成功,JUnit視窗不會顯示任何錯誤或失敗資訊,所以我們看到了如下所示的介面:
如果我們改變一下程式碼:
int testSum = 10;
- 1
修改程式碼後兩個整數的測試不再相等,程式輸出將如下所示:
同時,在JUnit視窗中會顯示測試出錯,並給出錯誤資訊:
2.2 JUnit註解
本節我們將介紹JUnit4支援的基本註解,下表是對這些索引的總結:
讓我們來看一個包含了以上註解的測試類的例子:
AnnotationsTest.java:
package com.javacodegeeks.junit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import java.util.ArrayList; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; public class AnnotationsTest { private ArrayList testList; /** * if the method isn't static, throw following exception: * java.lang.Exception: Method onceExecutedBeforeAll() should be static */ @BeforeClass public static void onceExecutedBeforeAll() { System.out.println("@BeforeClass: onceExecutedBeforeAll"); } @Before public void executedBeforeEach() { testList = new ArrayList(); System.out.println("@Before: executedBeforeEach"); } /** * If the method isn't static, throw following exception: * java.lang.Exception: Method onceExecutedAfterAll() should be static */ @AfterClass public static void onceExecutedAfterAll() { System.out.println("@AfterClass: onceExecutedAfterAll"); } @After public void executedAfterEach() { testList.clear(); System.out.println("@After: executedAfterEach"); } @Test public void EmptyCollection() { assertTrue(testList.isEmpty()); System.out.println("@Test: EmptyArrayList"); } @Test public void OneItemCollection() { testList.add("oneItem"); assertEquals(1, testList.size()); System.out.println("@Test: OneItemArrayList"); } @Ignore public void executionIgnored() { System.out.println("@Ignore: This execution is ignored"); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
執行以上測試,控制檯輸出如下所示:
JUnit視窗顯示如下資訊:
2.3 Junit斷言
本節我們將展示一些斷言方法。所有這些方法由Assert類提供,Assert類繼承自java.lang.Object。這些斷言能幫助你編寫測試用例,監測到失敗的測試。下表是對一些最常用的斷言方法的詳細介紹:
讓我們看一個上述斷言的例子:
AssertionsTest.java
package com.javacodegeeks.junit;
import static org.junit.Assert.*;
import org.junit.Test;
public class AssertionsTest { @Test public void test() { String obj1 = "junit"; String obj2 = "junit"; String obj3 = "test"; String obj4 = "test"; String obj5 = null; int var1 = 1; int var2 = 2; int[] arithmetic1 = {1,2,3}; int[] arithmetic2 = {1,2,3}; assertEquals(obj1, obj2); assertSame(obj3, obj4); assertNotSame(obj2, obj4); assertNotNull(obj1); assertNull(obj5); assertTrue(var1 != var2); assertFalse(var1 == var2); assertArrayEquals(arithmetic1, arithmetic2); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
從以上類中我們可以看到斷言方法是怎樣工作的。
- assertEquals():如果兩個物件相等,方法正常返回;否則在JUnit視窗中將顯示錯誤,該測試將被中斷。
- assertSame()與assertNotSame():測試兩個物件的引用是否指向了同一個物件。
- assertNull()與assertNotNull():測試一個變數是否是null或非null。
- assertTrue()與assertFalse():測試一個條件或變數時true或false.。
- assertArrayEquals():比較兩個陣列是否相等。如果相等,方法正常返回;否則在JUnit視窗中將顯示錯誤,該測試將被中斷。
3. 使用Eclipse實現完整的JUnit例子
本節我們將展示一個使用JUnit的完整例子。我們將講解怎樣建立和執行測試用例,以及怎樣使用JUnit的註解和斷言。
3.1 建立工程
建立一個名為JUnitGuide的java工程。建立一個包com.javacodegeeks.junit用來放被測試的類。對於測試類,一個好的實踐是建立一個專用於測試的原始碼包。這樣被測試類和測試類就被分在了不同的原始碼包中。建立test包:右鍵單擊你的工程, New–>Source Folder,命名原始碼包為test。
提示:建立原始碼包的另一種方式:右鍵單擊你的工程PropertiesJava Build Path,選擇Source 項,然後Add FolderCreate New Folder。輸入原始碼包名,點選finish。
你能看到在工程裡有兩個原始碼包:
你可以在test原始碼包中也建立一個名稱為com.javacodegeeks.junit的包,這樣你的測試類就不會被放在預設的包中。
3.2 建立要被測試的Java類
右擊src原始碼包,建立一個名為FirstDayAtScholl.java的類。該類的public方法將被測試。
FirstDayAtSchool.java
3.3 建立並執行JUnit測試用例
為已經存在的類FirstDayAtSchool.java建立測試用例:選中FirstDayAtSchool.java類,右擊選擇NewJUnit Test Case。改變原始碼包,將測試用例類放在test原始碼包中。確保選擇“JUnit 4 test”選項。
點選“Finish”。如果你的專案classpath下還沒有包含JUnit4,將會彈出如下所示的資訊讓你將JUnit4 Library新增進你的工程中。
(譯者注:也可以直接在test原始碼包中像建立普通類一樣建立一個測試用例類。只要該類中的方法用@Test註解標註上,就能引入JUnit框架,併成為測試用例。)
以下就是我們建立的名為FirstDayAtSchoolTest.java的測試用例。
FirstDayAtSchoolTest.java
右鍵Run AsJUnit Test,執行測試用例。
程式輸出如下所示:
JUnit視窗顯示如下:
此時JUnit視窗沒有任何錯誤或失敗。如果我們改變其中一個數組,讓它包含比預期更多的元素:
再次執行測試類,JUnit視窗將顯示有一個失敗的測試用例:
或者,我們再次改變陣列,讓它包含與預期不同的元素:
再次執行測試類,JUnit視窗將再次顯示一個測試用例fail:
3.4 使用@Ignore註解
在上面的例子中,讓我們看看怎樣使用@Ignore註解。在測試類FirstDayAtSchoolTest.java的testAddPencils()方法上標註@Ignore註解。通過這種方式,我們將該測試方法忽略掉,測試時不會執行。
執行測試,輸出如下所示:@Ignore註解的測試方法沒有執行。
JUnit視窗顯示如下:
現在,我們去掉testAddPencils()方法上的@Ignore註解,將@Ignore註解標註在FirstDayAtSchoolTest.java類上。
執行該測試類發現:整個測試用例將不會執行,所以控制檯上沒有任何輸出。JUnit視窗顯示如下資訊:
3.5 建立測試套件(suite tests)
本節,我們將展示如何建立測試套件。測試套件是來自於不同類的多個測試用例的集合。使用@Runwith和@Suite註解可以讓這些測試類一起執行。當你有許多測試類並且你希望這些測試類一起執行時,這是非常有用的。
在上一節類的基礎上,我們可以建立兩個測試類。一個測試prepareMyBag()方法,另一個測試addPencils()方法。兩個測試類如下所示:
PrepareMyBagTest.java.
AddPencilsTest.java.
我們來建立一個測試套件類來執行以上兩個測試類。測試套件類SuiteTest.java如下所示:
SuiteTest.java.
使用@Suite.SuiteClasses註解來定義哪些測試類將被包含進測試套件一起執行。
執行該測試套件,這兩個測試類將按照在@Suite.SuiteClasses註解中宣告的順序執行。
執行完畢後JUnit視窗顯示資訊如下:
3.6 建立引數化測試(parameterized tests)
本節我們將展示如何建立引數化的測試。我們將使用前面的2.1節中的類作為被測試類。
什麼時候一個測試類才能被認為是一個引數化的測試類呢?當測試類滿足下列條件的時候:
該類使用@RunWith(Parameterized.class)標註;
@RunWith註解告訴JUnit使用該註解標明的執行器執行測試用例,而不是使用JUnit內建的預設執行器。Parameterized是JUnit中的一個執行器,該執行器將使用一組不同的輸入來多次執行一個相同的測試用例。
該類有唯一的建構函式來建立測試資料;
該類有一個static方法用來產生並返回測試資料,該方法使用@Parameters註解標註;
該類有一個使用@Test標註的測試用例(譯者注:廢話嗎這不是)。
我們建立了一個新的測試類CalculateTest.java。該類遵循以上的規則。該類原始碼如下:
上如程式碼滿足以上的所有規則。addedNumbers方法使用@Parameters標註,返回陣列的集合。每一個數組包含一次測試執行的I/O資料。陣列中元素的個數必須和建構函式中引數的個數一致。在該例子中,一個數組包含三個元素,兩個代表要被相加的數,一個代表結果。
執行該測試用例,控制檯輸出如下所示:
JUnit視窗輸出資訊如下:
從上述資訊可以看出,該測試用例執行了4次,這正是使用@Parameters註解標註的方法中輸入的測試資料的個數。
3.7 規則(Rules)
本節我們將介紹JUnit4的一個新特性,叫做規則(Rules)。Rules允許我們靈活的增加或重新定義每一個測試方法的行為。為達到這個目的,使用@Rule註解標註測試類中public的域(fields)。這些域必須是MethodRule型別的,他們標明瞭一個測試方法的執行或結果報告作出了哪些改變。一個測試方法可以應用多個MethodRule。MethodRule介面有許多實現,如ErrorCollector,該規則允許測試用例在發現第一次錯誤是可以繼續執行;ExpectedException,該規則允許我們明確預期異常的型別及異常資訊;TestName,該規則允許我們在測試方法中獲取到當前測試方法的名稱;等等其他規則。除了JUnit已經定義的規則,開發者也可以自定義規則並在測試用例中使用。
下面,我們展示一個使用已經存在的TestName規則的例子。當一個測試類開始執行時TestName被呼叫。
NameRuleTest.java
如上所示:@Rule註解標註了一個public的域name,該域是MethodRule型別的。確切的說,該類是MethodRule介面的實現類MethodRule型別。然後我們就可以使用name在我們的測試方法中獲取當前正在執行的測試方法的名字了。
(譯者注:關於Rules的詳細說明,請檢視JUnit4官方文件:https://github.com/junit-team/junit4/wiki/Rules。執行裡面的例子,有疑問就修改例子測試,一目瞭然。Rules還是有不少用處的。)
3.8 策略(Categories)
JUnit的另一個新特性是策略,這允許你將特定的測試放在一起形成一個分組(group),並可以包含或排除某些分組。例如你可以將執行慢的測試用例與執行快的測試用例分開。JUnit提供了@Category註解為一個測試用例標明策略。以下是使用該新特性的例子。該特性從JUnit4.8開始支援。
首先,定義兩個策略,FastTests和SlowTests。一個策略可以是一個類或者一個藉口。
以上的程式碼中,我們在A類的b()方法上使用了@Category註解,標註該方法在SlowTests策略中。所以,@Category註解不僅可以標註整個類,也可以標註某一個測試方法。
以上的程式碼中,我們看到整個B類使用了@Category標註。在類上標註後,該類的所有測試方法都會自動包含進@Category中宣告的策略中。我們也可以看到,一個類或者一個測試方法可以屬於多個策略。
以上程式碼中,我們看到有一個名為SlowTestSuite_1的測試套件。一般情況下,我們認為策略就是一種測試套件。在該套件中,我們使用@IncludeCategory註解標明哪些策略將會被執行。在該例中,屬於SlowTests策略的所有方法將會被執行。因此,A類中的b()方法和B類中的c方法將被執行,他們都屬於SlowTests策略。
以上程式碼中,我們對測試套件做了一些修改,我們添加了一個新的註解@ExcludeCategory,該註解表明那些策略將被排除。在本例中,只有A類的b()方法會執行,因為他是唯一隻屬於SlowTests策略,並不屬於FastTests策略的測試方法。
通過以上兩個例子我們看到,A類的a()方法從未執行,因為它不屬於任何策略。
5. 命令列執行JUnit測試
通過使用org.junit.runner.JUnitCore 類,可以在Eclipse之外執行測試用例。該類提供了runClasses()方法允許執行一個或多個測試類。runClasses()方法的返回型別是一個org.junit.runner.Result物件。該物件可以蒐集所有測試的資訊。如果有失敗的測試用例,你可以使用org.junit.runner.notification.Failure物件來獲取失敗的測試用例的描述。
下面的流程展示瞭如何在Eclipse之外執行你的測試。
建立一個新類JunitRunner.java如下所示:
JunitRunner.java
我們選擇執行名為AssertionsTest的測試類。
開啟DOS視窗,並定位到JunitRunner.java所在的目錄(譯者注:本人的目錄是**\JUnitGuide\test\com\javacodegeeks\junit)。
編譯測試類和Runner類。
javac -classpath “D:\learnTech\test\junit-4.11.jar”;”D:\learnTech\test\hamcrest-core-1.3.0.jar”; AssertionsTest.java JunitRunner.java
和在Eclipse中一樣,我們需要將JUnit的jar包包含進classpath中。執行完上述命令。在Junitrunner.java所在的目錄下就生成了AssertionsTest.class 和JunitRunner.class兩個class檔案。如下所示:
在DOS視窗中,退回到test原始碼目錄(譯者注:本人的目錄是**\JUnitGuide\test)。
執行JunitRunner類。
java -classpath “D:\learnTech\test\junit-4.11.jar”;”D:\learnTech\test\hamcrest-core-1.3.0.jar”; JunitRunner
輸出結果為:
from: https://blog.csdn.net/Rainnnbow/article/details/52217058