網(wǎng)站建設(shè)要求百度指數(shù)疫情
單元測試
是什么?
單元測試(unit testing),是指對軟件中的最小可測試單元進(jìn)行檢查和驗證。至于“單元”的大小或范圍,并沒有一個明確的標(biāo)準(zhǔn),“單元”可以是一個方法、類、功能模塊或者子系統(tǒng)。
單元測試通常和白盒測試聯(lián)系到一起,如果單從概念上來講兩者是有區(qū)別的,不過我們通常所說的“單元測試”和“白盒測試”都認(rèn)為是和代碼有關(guān)系的,所以在某些語境下也通常認(rèn)為這兩者是同一個東西。還有一種理解方式,單元測試和白盒測試就是對開發(fā)人員所編寫的代碼進(jìn)行測試。
作用
1.幫助理解需求
單元測試應(yīng)該反映使用案例,把被測單元當(dāng)成黑盒測試其外部行為。
2. 提高實現(xiàn)質(zhì)量
單元測試不保證程序做正確的事,但能幫助保證程序正確地做事,從而提高實現(xiàn)質(zhì)量。
3.測試成本低
相比集成測試、驗收測試,單元測試所依賴的外部環(huán)境少,自動化程度高,時間短,節(jié)約了測試成本。
4.反饋速度快
單元測試提供快速反饋,把bug消滅在開發(fā)階段,減少問題流到集成測試、驗收測試和用戶,降低了軟件質(zhì)量控制的成本。
5.利于重構(gòu)
由于有單元測試作為回歸測試用例,有助于預(yù)防在重構(gòu)過程中引入bug。
6.文檔作用
單元測試提供了被測單元的使用場景,起到了使用文檔的作用。
7.對設(shè)計的反饋
一個模塊很難進(jìn)行單元測試通常是不良設(shè)計的信號,單元測試可以反過來指導(dǎo)設(shè)計出高內(nèi)聚、低耦合的模塊。
什么時候?qū)憜卧獪y試?
更多的時候是同時在實現(xiàn)代碼和單元測試。因為這樣既可以在實現(xiàn)的過程中隨時執(zhí)?單元測試來驗證,同時也避免分開寫時要重復(fù)理解?遍設(shè)計需求,??提?了效率,節(jié)約了時間。
這?特別要強(qiáng)調(diào)的?點是,有時候,寫單元測試和不寫單元測試,會直接影響到代碼的設(shè)計和實現(xiàn)。?如要寫?個有很多條件分?處理的函數(shù),如果不考慮單測,你很可能把這所有的邏輯寫在?個函數(shù)?。但是如果考慮到單測實現(xiàn)的簡潔,你就會把各個分?各寫成?個函數(shù),然后把分?邏輯另寫成?個函數(shù),最終意外達(dá)到了優(yōu)化代碼的?的。所以評判代碼或者設(shè)計好不好的?個準(zhǔn)則是看它容不容易測試。
什么時候可以不寫單元測試?
在個?的?作實踐中,很少遇到可以不寫單元測試的情況,當(dāng)然確實有過不?寫的時候。下面是可能遇到的幾種情況,請自行掂量。
-
函數(shù)邏輯太復(fù)雜了,歷史上也從沒有?為它寫過單測,代碼的reviewer也沒有要求我寫。
-
代碼的重要性不夠,都是自己寫自己維護(hù)的,即使代碼有問題也不會有什么重要影響的。有些接?的?函數(shù),典型如Main函數(shù)…
-
寫對應(yīng)的單元測試?常的復(fù)雜,甚??法寫。這時候很可能
-
- 需要修改設(shè)計,必須讓你的設(shè)計易于單元測試
- 需要增強(qiáng)單元測試框架,框架功能不夠,不能很好?持某種場景下的單測。
- 實在想不起來還有什么情況…也許有些涉及到?戶交互的UI單元?
單元測試編寫規(guī)范
-
好的單元測試必須遵守AIR原則。A:Automatic(自動化),I:Independent(獨立性),R:Repeatable(可重復(fù))
-
Automatic(自動化) 單元測試應(yīng)該是全自動執(zhí)行的,并且非交互式的。測試用例通常是被定期執(zhí)行的,執(zhí)行過程必須完全自動化才有意義。輸出結(jié)果需要人工檢查的測試不是一個好的單元測試。單元測試中不準(zhǔn)使用System.out來進(jìn)行人肉認(rèn)證,必須使用assert來驗證。
-
Independent(獨立性) 保持單元測試的獨立性。為了保證單元測試穩(wěn)定可靠且便于維護(hù),單元測試用例之間絕不能互相調(diào)用,也不能依賴執(zhí)行的先后次序。
-
Repeatable(可重復(fù)) 單元測試是可以重復(fù)執(zhí)行的,不能受到外界環(huán)境的影響。單元測試通常會被放到持續(xù)集成中,每次有代碼check in時單元測試都會被執(zhí)行。如果單測對外部環(huán)境有依賴,容易導(dǎo)致持續(xù)繼承機(jī)制的不可用。
-
對于單元測試,要保證測試粒度足夠小,有助于精確定位問題,單測粒度至多是類級別,一般是方法級別。
-
核心業(yè)務(wù)、核心應(yīng)用、核心模塊的增量代碼確保單元測試通過。
-
單元測試代碼必須寫在如下工程目錄:src/test/java,不允許寫在業(yè)務(wù)代碼目錄下。
-
每個單元測試應(yīng)該有個好名字,讓??看就知道是做什么測試,如果名字不能說明問題也要加上完整的注釋。?如 testSortNumbers_withDuplicated, 意味SortNumbers函數(shù)的單元測試來驗證有重復(fù)數(shù)字的情況。
代碼覆蓋率
是什么?
代碼覆蓋率是對整個測試過程中被執(zhí)行的代碼的衡量,它能測量源代碼中的哪些語句在測試中被執(zhí)行,哪些語句尚未被執(zhí)行。
為什么要測量代碼覆蓋率?
眾所周知,測試可以提高軟件版本的質(zhì)量和可預(yù)測性。但是,你知道你的單元測試甚至是你的功能測試實際測試代碼的效果如何嗎?是否還需要更多的測試?
這些是代碼覆蓋率可以試圖回答的問題??傊?#xff0c;出于以下原因我們需要測量代碼覆蓋率:
- 了解我們的測試用例對源代碼的測試效果
- 了解我們是否進(jìn)行了足夠的測試
- 在軟件的整個生命周期內(nèi)保持測試質(zhì)量
注:代碼覆蓋率不是靈丹妙藥,覆蓋率測量不能替代良好的代碼審查和優(yōu)秀的編程實踐。
通常,我們應(yīng)該采用合理的覆蓋目標(biāo),力求在代碼覆蓋率在所有模塊中實現(xiàn)均勻覆蓋,而不是只看最終數(shù)字的是否高到令人滿意。
舉例:假設(shè)代碼覆蓋率只在某一些模塊代碼覆蓋率很高,但在一些關(guān)鍵模塊并沒有足夠的測試用例覆蓋,那樣雖然代碼覆蓋率很高,但并不能說明產(chǎn)品質(zhì)量就很高。
代碼覆蓋率的指標(biāo)種類
代碼覆蓋率工具通常使用一個或多個標(biāo)準(zhǔn)來確定你的代碼在被自動化測試后是否得到了執(zhí)行,常見的覆蓋率報告中看到的指標(biāo)包括:
- 函數(shù)覆蓋率:定義的函數(shù)中有多少被調(diào)用
- 語句覆蓋率:程序中的語句有多少被執(zhí)行
- 分支覆蓋率:針對 if…else、case 等分支語句,看代碼中設(shè)計的分支是否都被測試到了。針對 if(條件1),只要條件 1 取 true 和 false 都執(zhí)行過,則這個分支就完全覆蓋了。
- 條件覆蓋率:條件覆蓋率可以看作是對分支覆蓋率的補(bǔ)充。每一個分支條件表達(dá)式中,所有條件的覆蓋。
- 行覆蓋率:有多少行的源代碼被測試過
對比main方法優(yōu)點
比代碼中寫main 方法測試的好處:
- 可以書寫一系列的 測試方法,對項目所有的 接口或者方法進(jìn)行單元測試。
- 啟動后,自動化測試,并判斷執(zhí)行結(jié)果, 不需要人為的干預(yù)
- 只需要查看最后結(jié)果,就知道整個項目的方法接口是否通暢。。
- 每個單元測試用例相對獨立, 由Junit 啟動,自動調(diào)用。 不需要添加額外的調(diào)用語句。
而main 方法不一樣。
對多個方法調(diào)用。 需要添加打印或者輸出語句。
添加了新的測試方法。 需要在main方法添加方法調(diào)用。
不能形成整體的測試結(jié)果。
需要對打印或者輸出結(jié)果進(jìn)行人為的判斷。
JUnit
JUnit 是一個 Java 編程語言的單元測試框架。JUnit 在測試驅(qū)動的開發(fā)方面有很重要的發(fā)展,是起源于 JUnit 的一個統(tǒng)稱為 xUnit 的單元測試框架之一。
注解
JUnit 提供以下注解來編寫測試。
注解 | 描述 |
---|---|
@RunWith | 用于設(shè)置測試運行器。例如,我們可以通過 @RunWith(SpringJUnit4ClassRunner.class) 讓測試運行于 Spring 測試環(huán)境。 |
@Before | 帶注解的方法將在測試類中的每個測試方法之前運行。 |
@After | 帶注解的方法將在測試類中的每個測試方法之后運行。 |
@BeforeClass | 帶注解的方法將在測試類中的所有測試方法之前運行。 此方法必須是靜態(tài)的。 |
@AfterClass | 帶注解的方法將在測試類中的所有測試方法之后運行。 此方法必須是靜態(tài)的。 |
@Test | 用于將方法標(biāo)記為 junit 測試 |
@Ignore | 它用于禁用或忽略測試套件中的測試類或方法。 |
@Rule | 引用規(guī)則,在一個class中所有的@Test標(biāo)注過的測試方法都會共享這個Rule |
一個單元測試類執(zhí)行順序為:
@BeforeClass –> @Before –> @Test –> @After –> `@AfterClass
每一個測試方法的調(diào)用順序為:
@Before –> @Test –> @After
編寫測試
在 JUnit 中,測試方法帶有@Test
注解。 為了運行該方法,JUnit 首先構(gòu)造一個新的類實例,然后調(diào)用帶注解的方法。 測試拋出的任何異常將由 JUnit 報告為失敗。 如果未引發(fā)任何異常,則假定測試成功。
public class Demo1 {@BeforeClasspublic static void setup() {System.out.println("@BeforeClass");}@Beforepublic void setupThis() {System.out.println("@Before");}@Testpublic void method() {System.out.println("測試");}@Afterpublic void tearThis() {System.out.println("@After");}@AfterClasspublic static void tear() {System.out.println("@AfterClass");}
}
public class Demo2 {@Testpublic void test1() {System.out.println("@Test");}@Ignore@Testpublic void testIgnore() {System.out.println("@Ignore");}
}
斷言方法
**斷言(assertion)**是一種在程序中的一階邏輯(如:一個結(jié)果為真或假的邏輯判斷式),目的為了表示與驗證軟件開發(fā)者預(yù)期的結(jié)果——當(dāng)程序執(zhí)行到斷言的位置時,對應(yīng)的斷言應(yīng)該為真。若斷言不為真時,程序會中止執(zhí)行,并給出錯誤信息。
Method | Description |
---|---|
assertNull(java.lang.Object object) | 檢查對象是否為空 |
assertNotNull(java.lang.Object object) | 檢查對象是否不為空 |
assertEquals(long expected, long actual) | 檢查long類型的值是否相等 |
assertFalse(boolean condition) | 檢查條件是否為假 |
assertTrue(boolean condition) | 檢查條件是否為真 |
assertNotSame(java.lang.Object unexpected, java.lang.Object actual) | 檢查兩個對象引用是否不引用統(tǒng)一對象(即對象不等) |
案例:
public class Demo3 {@Testpublic void testAssertNull() {String str = null;assertNull(str);}@Testpublic void testAssertNotNull() {String str = "hello Java!!";assertNotNull(str);}@Testpublic void testAssertEqualsLong() {long long1 = 2;long long2 = 2;assertEquals(long1, long2);}@Testpublic void testAssertTrue() {List<String> list = new ArrayList<>();assertTrue(list.isEmpty());}@Testpublic void testAssertFalse() {List<String> list = new ArrayList<>();list.add("hello");assertFalse(list.isEmpty());}@Testpublic void testAssertSame() {String str1 = "hello world!!";String str2 = "hello world!!";assertSame(str2, str1);}@Testpublic void testAssertNotSame() {String str1 = "hello world!!";String str3 = "hello Java!!";assertNotSame(str1, str3);}
}
期望異常測試
有兩種方法實現(xiàn):
1. @Test(expected…)
@Test注解有一個可選的參數(shù),"expected"允許你設(shè)置一個Throwable的子類
2. ExpectedException
如果要使用JUnit框架中的ExpectedException類,需要聲明ExpectedException異常。
案例:
public class Demo4 {@Rulepublic ExpectedException thrown = ExpectedException.none();public void division() {int i = 5 / 0;}@Test(expected = ArithmeticException.class)public void test1() {division();}@Test()public void test2() {thrown.expect(ArithmeticException.class);division();}}
優(yōu)先級測試
將測試方法構(gòu)成測試回環(huán)的時候,就需要確定測試方法執(zhí)行順序,以此記錄。
@FixMethodOrder
是控制@Test方法執(zhí)行順序的注解,她有三種選擇
MethodSorters.JVM
按照J(rèn)VM得到的順序執(zhí)行
MethodSorters.NAME_ASCENDING
按照方法名字順序執(zhí)行
MethodSorters.DEFAULT
按照默認(rèn)順序執(zhí)行 以確定的但是不可預(yù)期的順序執(zhí)行(hashcode大小)
@(MethodSorters.JVM)
public class Demo5 {@Testpublic void test2() {System.out.println("test2");}@Testpublic void test1() {System.out.println("test1");}@Testpublic void test3() {System.out.println("test3");}}
參數(shù)化測試
參數(shù)化測試主要解決一次性進(jìn)行多個測試用例的測試。其主要思想是,將多個測試用例按照,{輸入值,輸出值}(輸入值可以是多個)的列表方式進(jìn)行測試。
//(1)步驟一:測試類指定特殊的運行器org.junit.runners.Parameterized
@RunWith(Parameterized.class)
public class Demo6 {// (2)步驟二:為測試類聲明變量,分別用于存放期望值和測試所用數(shù)據(jù)。private final int expected;private final int a;private final int b;public Demo6(int expected, int a, int b) {this.expected = expected;this.a = a;this.b = b;}// (4)步驟四:為測試類聲明一個使用注解 org.junit.runners.Parameterized.Parameters 修飾的,// 返回值為java.lang.Iterable 的公共靜態(tài)方法,并在此方法中初始化所有需要測試的參數(shù)對。@Parameterspublic static Iterable<Integer[]> getTestParamters() {return Arrays.asList(new Integer[][]{{2, 1, 1}, {3, 2, 1}, {4, 3, 1}});}// (5)步驟五:編寫測試方法,使用定義的變量作為參數(shù)進(jìn)行測試。 @Testpublic void testAdd() {Demo calculator = new Demo();System.out.println("輸入?yún)?shù) " + a + " and " + b + ",預(yù)期值 " + expected);assertEquals(expected, calculator.add(a, b));}
}
超時測試
如果一個測試用例比起指定的毫秒數(shù)花費了更多的時間,那么 Junit 將自動將它標(biāo)記為失敗。timeout 參數(shù)和 @Test注釋一起使用?,F(xiàn)在讓我們看看活動中的 @test(timeout)。
public class Demo7 {@Test(timeout = 1000)public void testTimeout() throws InterruptedException {TimeUnit.SECONDS.sleep(2);System.out.println("Complete");}
}
上面測試會失敗,在一秒后會拋出異常 org.junit.runners.model.TestTimedOutException: test timed out after 1000 milliseconds
Mock
為什么要使用 mock
-
Mock 的意思是模擬,它可以用來對系統(tǒng)、組件或類進(jìn)行隔離。
在測試過程中,我們通常關(guān)注測試對象本身的功能和行為,而對測試對象涉及的一些依賴,僅僅關(guān)注它們與測試對象之間的交互(比如是否調(diào)用、何時調(diào)用、調(diào)用的參數(shù)、調(diào)用的次數(shù)和順序,以及返回的結(jié)果或發(fā)生的異常等),并不關(guān)注這些被依賴對象如何執(zhí)行這次調(diào)用的具體細(xì)節(jié)。
因此,Mock 機(jī)制就是使用 Mock 對象替代真實的依賴對象,并模擬真實場景來開展測試工作。
使用 Mock 對象完成依賴關(guān)系測試的示意圖如下所示:
可以看出,在形式上,Mock 是在測試代碼中直接 Mock 類和定義 Mock 方法的行為,通常測試代碼和 Mock 代碼放一起。因此,測試代碼的邏輯從測試用例的代碼上能很容易地體現(xiàn)出來。
Mockito 中常用方法
Mockito的使用,一般有以下幾種組合:
- do/when:包括doThrow(…).when(…)/doReturn(…).when(…)/doAnswer(…).when(…) ,返回值為void時使用
- given/will:包括given(…).willReturn(…)/given(…).willAnswer(…)
- when/then: 包括when(…).thenReturn(…)/when(…).thenAnswer(…)
Mock 方法
mock 方法來自 org.mockito.Mock
,它表示可以 mock 一個對象或者是接口。
public static <T> T mock(Class<T> classToMock)
- classToMock:待 mock 對象的 class 類。
- 返回 mock 出來的類
實例:使用 mock 方法 mock 一個類
Random random = Mockito.mock(Random.class);
對 Mock 出來的對象進(jìn)行行為驗證和結(jié)果斷言
驗證是校驗待驗證的對象是否發(fā)生過某些行為,Mockito 中驗證的方法是:verify。
@Testvoid addTest() {Random random = Mockito.mock(Random.class);System.out.println(random.nextInt());Mockito.verify(random).nextInt();// Mockito.verify(random, Mockito.times(2)).nextInt();}
使用 verify 驗證:
Verify 配合 time() 方法,可以校驗?zāi)承┎僮靼l(fā)生的次數(shù)。
@Testvoid addTest() {Random random = Mockito.mock(Random.class);System.out.println(random.nextInt());Mockito.verify(random, Mockito.times(2)).nextInt();}
斷言使用到的類是 Assert.
Random random = Mockito.mock(Random.class, "test");
Assert.assertEquals(100, random.nextInt());
輸出結(jié)果:
org.opentest4j.AssertionFailedError:
Expected :100
Actual :0
當(dāng)使用 mock 對象時,如果不對其行為進(jìn)行定義,則 mock 對象方法的返回值為返回類型的默認(rèn)值。
給 Mock 對象打樁
樁,或稱樁代碼,是指用來代替關(guān)聯(lián)代碼或者未實現(xiàn)代碼的代碼。如果函數(shù)B用B1來代替,那么,B稱為原函數(shù),B1稱為樁函數(shù)。打樁就是編寫或生成樁代碼。
打樁可以理解為 mock 對象規(guī)定一行的行為,使其按照我們的要求來執(zhí)行具體的操作。在 Mockito 中,常用的打樁方法為
方法 | 含義 |
---|---|
when().thenReturn() | Mock 對象在觸發(fā)指定行為后返回指定值 |
when().thenThrow() | Mock 對象在觸發(fā)指定行為后拋出指定異常 |
when().doCallRealMethod() | Mock 對象在觸發(fā)指定行為后調(diào)用真實的方法 |
thenReturn() 代碼示例
@Testvoid addTestOngoingStubbing() {MockitoAnnotations.initMocks(this);Mockito.when(random.nextInt()).thenReturn(1); //打樁,指定返回值System.out.println(random.nextInt());}
輸出1
Mockito 中常用注解
可以代替 Mock 方法的 @Mock 注解
Shorthand for mocks creation - @Mock annotation
Important! This needs to be somewhere in the base class or a test runner:
快速 mock 的方法,使用 @mock
注解。
mock 注解需要搭配 MockitoAnnotations.initMocks(testClass) 方法一起使用。
@Mockprivate Random random;@Testvoid addTestAnnotations() {//開啟注解,否則空指針MockitoAnnotations.initMocks(this);System.out.println(random.nextInt());Mockito.verify(random).nextInt();}
Spy 方法與 @Spy 注解
spy() 方法與 mock() 方法不同的是
- 被 spy 的對象會走真實的方法,而 mock 對象不會
- spy() 方法的參數(shù)是對象實例,mock 的參數(shù)是 class
示例:spy 方法與 Mock 方法的對比
@Testvoid addTestMockAndSpyDifferent() {Demo mock = Mockito.mock(Demo.class);Assert.assertEquals(0, mock.add(1, 2));Demo spy = Mockito.spy(new Demo());Assert.assertEquals(3, spy.add(1, 2));}
輸出結(jié)果
// 第一個 Assert 斷言失敗,因為沒有給 Demo 對象打樁,因此返回默認(rèn)值
java.lang.AssertionError: expected:<3> but was:<0>
預(yù)期:3
實際:0
使用 @Spy
注解代碼示例
@Spyprivate Demo demo;@Testvoid addTestAnnotations() {MockitoAnnotations.initMocks(this);int res = demo.add(1, 2);Assert.assertEquals(3, res);// Assert.assertEquals(4,res);}
Spring Boot 中使用 JUnit
Spring 框架提供了一個專門的測試模塊(spring-test),用于應(yīng)用程序的集成測試。 在 Spring Boot 中,你可以通過spring-boot-starter-test啟動器快速開啟和使用它。
Spring Boot 測試
// 獲取啟動類,加載配置,確定裝載 Spring 程序的裝載方法,它回去尋找 主配置啟動類(被 @SpringBootApplication 注解的)
@SpringBootTest
// 讓 JUnit 運行 Spring 的測試環(huán)境, 獲得 Spring 環(huán)境的上下文的支持
@RunWith(SpringRunner.class)
public class Demo1 {@Autowiredprivate UserService userService;@Testpublic void getUser() {User user = userService.getUser(1);Assert.assertEquals("bob",user.getName());}
}
@SpringBootTest - webEnvironment
MOCK
:加載 WebApplicationContext 并提供一個 Mock 的 Servlet 環(huán)境,此時內(nèi)置的 Servlet 容器并沒有正式啟動。RANDOM_PORT
:加載 EmbeddedWebApplicationContext 并提供一個真實的 Servlet 環(huán)境,然后使用一個隨機(jī)端口啟動內(nèi)置容器。DEFINED_PORT
:這個配置也是通過加載 EmbeddedWebApplicationContext 提供一個真實的 Servlet 環(huán)境,但使用的是默認(rèn)端口,如果沒有配置端口就使用 8080。NONE
:加載 ApplicationContext 但并不提供任何真實的 Servlet 環(huán)境。
在 Spring Boot 中,@SpringBootTest 注解主要用于測試基于自動配置的 ApplicationContext,它允許我們設(shè)置測試上下文中的 Servlet 環(huán)境。
在多數(shù)場景下,一個真實的 Servlet 環(huán)境對于測試而言過于重量級,通過 MOCK 環(huán)境則可以緩解這種環(huán)境約束所帶來的困擾
@RunWith 注解與 SpringRunner
在上面的示例中,我們還看到一個由 JUnit 框架提供的 @RunWith 注解,它用于設(shè)置測試運行器。例如,我們可以通過 @RunWith(SpringJUnit4ClassRunner.class) 讓測試運行于 Spring 測試環(huán)境。
雖然這我們指定的是 SpringRunner.class,實際上,**SpringRunner 就是 SpringJUnit4ClassRunner 的簡化,它允許 JUnit 和 Spring TestContext 整合運行,而 Spring TestContext 則提供了用于測試 Spring 應(yīng)用程序的各項通用的支持功能。
Spring MVC 測試
當(dāng)你想對 Spring MVC 控制器編寫單元測試代碼時,可以使用@WebMvcTest
注解。它提供了自配置的 MockMvc,可以不需要完整啟動 HTTP 服務(wù)器就可以快速測試 MVC 控制器。
使用@WebMvcTest
注解時,只有一部分的 Bean 能夠被掃描得到,它們分別是:
- @Controller
- @ControllerAdvice
- @JsonComponent
- Filter,WebMvcConfigurer,HandlerMethodArgumentResolver
- 其他常規(guī)的@Component(包括@Service、@Repository等)Bean 則不會被加載到 Spring 測試環(huán)境上下文中。
注意:
- 如果報錯:
java.lang.IllegalStateException: Found multiple @SpringBootConfiguration annotated classes......
,在類上添加注解@ContextConfiguration(classes = {測試啟動類.class})
,加載配置類
@RunWith(SpringRunner.class)
@WebMvcTest(UserController.class)
@Slf4j
public class Demo2 {@Autowiredprivate MockMvc mvc;@MockBeanprivate UserService userService;@Beforepublic void setUp() {//打樁Mockito.when(userService.getUser(1)).thenReturn(new User(1, "張三"));}@Testpublic void getUser() throws Exception {MvcResult mvcResult = mvc.perform(MockMvcRequestBuilders.get("/user/1"))//構(gòu)建請求, 測試的相對地址.andExpect(status().isOk()) // 期待返回狀態(tài)嗎碼200.andExpect(jsonPath("$.name").value(IsEqual.equalTo("張三"))) // 這里是期待返回值是 張三.andDo(print())//打印輸出流.andReturn();//返回結(jié)果String content = mvcResult.getResponse().getContentAsString(Charset.defaultCharset());log.info("返回結(jié)果:{}", content);}
}
Spring Boot Web 測試
當(dāng)你想啟動一個完整的 HTTP 服務(wù)器對 Spring Boot 的 Web 應(yīng)用編寫測試代碼時,可以使用@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)注解開啟一個隨機(jī)的可用端口。Spring Boot 針對 REST 調(diào)用的測試提供了一個 TestRestTemplate 模板,它可以解析鏈接服務(wù)器的相對地址。
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Slf4j
public class Demo3 {@Autowiredprivate TestRestTemplate restTemplate;@Testpublic void getUser(){ResponseEntity<User> result = restTemplate.getForEntity("/user/2", User.class);User body = result.getBody();Assert.assertThat(body, Matchers.notNullValue());log.info("User:{}", body);}
}
使用 @DataJpaTest 注解測試數(shù)據(jù)訪問組件
如要需要使用真實環(huán)境中的數(shù)據(jù)庫進(jìn)行測試,需要替換掉默認(rèn)規(guī)則,使用@AutoConfigureTestDatabase(replace = Replace.NONE)
注解:
@RunWith(SpringRunner.class)
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class Demo4 {@Autowiredprivate UserRepository userRepository;@Testpublic void getUser(){User user = userRepository.findById(2).orElse(null);assert user != null;Assert.assertThat("tom", Matchers.is(user.getName()));}
}
Spring Service層測試
@RunWith(SpringRunner.class)
public class Demo6 {@Autowiredprivate UserService userService;@MockBeanprivate UserRepository userRepository;@Testpublic void testGetUser() {User user1 = new User(1, "zs");Mockito.when(userRepository.findById(1)).thenReturn(Optional.of(user1));User user2 = userService.getUser(1);Assert.assertEquals(user1.getId(), user2.getId());}@TestConfigurationpublic static class prepareOrderService {@Beanpublic UserService getGlobalExceptionMsgSendService() {return new UserService();}}}
rElse(null);
assert user != null;
Assert.assertThat(“tom”, Matchers.is(user.getName()));
}
}
## Spring Service層測試```java
@RunWith(SpringRunner.class)
public class Demo6 {@Autowiredprivate UserService userService;@MockBeanprivate UserRepository userRepository;@Testpublic void testGetUser() {User user1 = new User(1, "zs");Mockito.when(userRepository.findById(1)).thenReturn(Optional.of(user1));User user2 = userService.getUser(1);Assert.assertEquals(user1.getId(), user2.getId());}@TestConfigurationpublic static class prepareOrderService {@Beanpublic UserService getGlobalExceptionMsgSendService() {return new UserService();}}}