CodeGym /Java Blog /Toto sisi /使用 JUnit 在 Java 中進行單元測試
John Squirrels
等級 41
San Francisco

使用 JUnit 在 Java 中進行單元測試

在 Toto sisi 群組發布

什麼是 Java 中的單元測試?

在開始學習 Java 中的 JUnit 之前,讓我們簡要概述一下單元測試是什麼以及為什麼它如此流行(如果您已經知道這些東西,請跳到“我如何用 Java 編寫 JUnit 測試?”)。Java 中的單元測試使大規模軟件開發更加高效和輕鬆。它可以幫助個人和團隊減少無數小時的調試時間並極大地簡化協作過程。 使用 JUnit 在 Java 中進行單元測試 - 1

https://junit.org/junit4/

單元測試的基本思想是:編寫單個功能的原子測試(稱為單元測試),並在測試並確保之前的功能正常後慢慢添加更多功能。這是一個極其簡單但功能強大的想法。作為此過程的外觀示例,假設您正在構建一個虛擬科學計算器。除了明顯的算術運算符 ( +, -, x, %) 之外,此計算器還具有需要其他子功能才能在其中運行的高級功能。要計算指數,您的計算器需要能夠正確乘法。因此,構建和測試此計算器的單元測試方法是:
  • 寫一個加法函數。仔細測試它,改變它,重複直到它起作用。
  • 對減法、乘法、除法函數執行相同的操作。
  • 使用這些基本運算符編寫更高級的運算符函數(如指數),然後也測試這些函數。
這確保了構建其他較小子功能的功能不僅可以正常工作,而且不會在其中包含錯誤的子功能。例如,如果我正在測試指數函數並且出現問題,我知道錯誤可能不在乘法子功能中,因為乘法函數已經過廣泛測試。這極大地消除了我需要回溯和檢查以查找錯誤的代碼總量。希望這個簡單的例子可以清楚地說明圍繞單元測試的思維過程是如何構建的。但是單元測試如何與軟件開發過程的其餘部分交互?如果您有更複雜的功能,需要能夠一起工作和交流怎麼辦?單元測試不足以確保如此復雜的功能可以一起正常工作。事實上,這只是軟件測試四個級別的第一步(我使用大寫字母是因為我指的是行業標准或最常見的軟件測試方法)。最後三個步驟是集成測試系統測試驗收測試. 這些可能都意味著您認為它們所做的,但讓我澄清一下:集成測試是我們要做的,以確保上面提到的那些“複雜功能”能夠正確地相互交互。(例如,確保計算器可以處理“3 + 7 * 4 - 2”)系統測試是測試特定係統的整體設計;通常有多個複雜功能的系統在一個產品中協同工作,因此您將這些系統分組並單獨測試它們。(例如,如果你正在構建一個圖形計算器,你首先要構建算術“系統”來處理數字,測試直到它按預期工作,然後你將構建並測試圖形“系統”來處理取款,因為它將建立在算術系統之上)。驗收測試是用戶級測試;它正在查看是否所有系統都可以同步工作以創建成品以供用戶接受(例如,用戶測試計算器)。軟件開發人員有時會忽略流程的最後一步,因為公司通常會讓其他員工單獨部署用戶(測試版)測試。

如何用 Java 編寫 JUnit 測試?

現在您對單元測試的好處和局限性有了更清晰的認識,讓我們來看看一些代碼!我們將使用一種流行的 Java 測試框架,稱為 JUnit(另一個流行的框架是 TestNG,如果您願意,也可以使用它。它們在語法上非常相似;TestNG 的靈感來自 JUnit)。您可以在此處下載並安裝 JUnit 。對於這個示例代碼,我們將繼續我之前提到的“科學計算器”示例;腦洞大開非常簡單,測試代碼也超級簡單。傳統做法是為每個類編寫單獨的測試類,這就是我們要做的。假設此時,我們有一個Math.java包含所有數學函數(包括Math.add)的文件,我們正在編寫一個MathTests.java文件在同一個包中。現在讓我們設置導入語句和類主體:(可能的 JUnit 面試問題:您可能會被問到將 JUnit 測試放在哪里以及是否需要導入源文件。如果您將測試類編寫在與你的主類,那麼你不需要在測試類中為你的源文件導入任何語句。否則,確保你正在導入你的源文件!)

import org.junit.jupiter.Test;    //gives us the @Test header
import static org.junit.jupiter.api.Assertions.assertEquals; //less typing :) 

public class MathTests {
	//...
}
第一個 import 語句給了我們@Test標題。我們@Test直接在每個測試函數定義的頂部寫上 ' ',以便 JUnit 知道這是一個可以單獨運行的單一單元測試。稍後,我將向您展示如何使用此標頭運行特定的單元測試。第二個 import 語句為我們節省了一些輸入。我們用來測試函數的主要 JUnit 函數是 to Assert.assertEquals(),它有兩個參數(實際值和預期值)並確保它們相等。有了第二個 import 語句,我們只需鍵入 ' assertEquals(...',而不必每次都指定它是哪個包的一部分。現在讓我們寫一個非常簡單的測試用例來驗證 2 + 2 確實是 4!

import org.junit.jupiter.Test; // gives us the @Test header
import static org.junit.jupiter.api.Assertions.assertEquals; // less typing :) 


public class MathTests {
	@Test
	public void add_twoPlusTwo_returnsFour(){
	final int expected = 4;
	final int actual = Math.add(2, 2);
	assertEquals(“2+2 is 4”, actual, expected);
	}
}
讓我們回顧一下測試函數的五行中的每一行以及它們的作用: 第 5 行:此@Test標頭指定下面的函數定義add_twoPlusTwo_returnsFour()確實是 JUnit 可以單獨運行的測試函數。第 6 行:這是我們測試用例的函數簽名。測試用例總是非常單一;他們只測試一個特定的例子,比如 2+2=4。通常以“ [function]_[params]_returns[expected]()”的形式命名您的測試用例,其中[function]是您正在測試的函數的名稱,[params]是您正在測試的特定示例參數,[expected]是函數的預期返回值。測試函數幾乎總是有一個返回類型 ' void' 因為整個函數的要點是運行assertEquals, 無論您的測試是否通過,它都會輸出到控制台;您不需要在任何地方返回任何其他數據。第 7 行:我們聲明一個final返回類型為 的 ' ' 變量Math.add (int),並按照約定將其命名為 'expected'。它的值就是我們期待的答案(4)。第 8 行:我們聲明一個final返回類型為 的 ' ' 變量Math.add (int),並按照約定將其命名為 'actual'。它的值是 的結果Math.add(2, 2)。第九行:黃金線。這是比較實際值和預期值的線,告訴我們只有當它們相等時我們才能通過測試。傳遞的第一個參數“2+2 is 4”是對測試函數的描述。

如果我的函數應該拋出異常怎麼辦?

如果您的特定測試示例應該拋出異常而不是斷言實際值和預期值相等,那麼 JUnit 有一種方法可以在標頭中闡明這一點@Test。讓我們看下面的一個例子。Math.java假設我們在called中有一個函數Math.divide,我們想確保輸入不能被 0 除。相反,嘗試調用Math.divide(a, 0)任何 'a' 值應該拋出異常 ( ArithmeticException.class)。我們在標頭中這樣指定:

import org.junit.jupiter.Test; // gives us the @Test header
import static org.junit.jupiter.api.Assertions.assertEquals; // less typing :) 


public class MathTests {
	@Test (expectedExceptions = ArithmeticException.class)
	public void divide_byZero_throwsException() throws ArithmeticException{
	Math.divide(1, 0);
	}
}
您可以為 設置多個異常expectedExceptions,只需確保使用方括號和逗號來列出您的異常類,例如:

expectedException = {FirstException.class, SecondException.class, … }

如何在 Java 中運行我的 JUnit 測試?

如何將 JUnit 添加到 IntelliJ:https://stackoverflow.com/questions/19330832/setting-up-junit-with-intellij-idea 您可以像通常運行測試一樣運行您的項目。在測試類中運行所有測試將按字母順序運行它們。在 JUnit 5 中,您可以通過添加標記來為測試添加優先級@Order。一個例子:

@TestMethodOrder(OrderAnnotation.class)
public class Tests {
…
@Test
@Order(2)
public void a_test() { … }

@Test
@Order (1)
public void b_test() { … }
…
}
即使在代碼中按字母順序a_test()排在前面,也會在此處運行在前面,因為 1 按順序排在 2 之前。這就是 JUnit 基礎知識的全部內容。現在,讓我們解決幾個常見的 JUnit 面試問題,並在此過程中了解更多關於 JUnit 的知識! b_test()b_test()a_test()

JUnit 面試問題(附加信息)

在這裡,我收集了最流行的 JUnit 面試問題。如果你有什麼要補充的——請在下面的評論中隨意添加。 問:您可以在測試方法中調用什麼方法來使測試自動失敗? A: fail(“這裡有錯誤描述!”); 問:你正在測試一個 Dog 類;要測試 Dog 對象,您需要先實例化它,然後才能對其運行測試。所以你寫了一個 setUp() 函數來實例化 Dog。您只想在所有測試期間運行此函數一次。您必須直接在 setUp() 函數簽名上方放置什麼,以便 JUnit 知道在運行測試之前運行 setUp()? 答: @BeforeClass(JUnit 5 中的@BeforeAll) 問:上面描述的 setUp() 函數的函數簽名必須是什麼? A: public static void。任何帶有@BeforeClass(JUnit 5 中的@BeforeAll)或@AfterClass(JUnit 5 中的@AfterAll)的函數都需要是靜態的。 問:您已經完成了 Dog 類的測試。您編寫 void tearDown() 函數來清理數據並在每次測試後將信息打印到控制台。您希望此函數在每次測試後運行。您必須直接在 tearDown() 函數簽名上方放置什麼,以便 JUnit 知道在運行每個測試後運行 tearDown()? 答: @After(JUnit 5 中的@AfterEach)
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION