public class HelloTest {
@Test
public void hello() {
System.out.println("hello world");
}
}
在 Idea 中點擊 helle() ⽅法名,選擇 Run hello() 即可運⾏,執⾏完畢控制檯打印信息如下:
hello world
證明⽅法執⾏成功。
測試服務
⼤多數情況下都是需要測試項⽬中某⼀個服務的準確性,這個時候往往需要 Spring Boot 啓動後的上下⽂環
境,對於這種情況只需要添加兩個註解即可⽀持。我們創建⼀個 HelloService 服務來演示。
public interface HelloService {
public void sayHello();
}
創建⼀個它的實現類:
@Service
public class HelloServieImpl implements HelloService {
@Override
public void sayHello() {
System.out.println("hello service");
}
}
在這個實現類中 sayHello() ⽅法輸出了字符串:"hello service"。
爲了可以在測試中獲取到啓動後的上下⽂環境(Beans),Spring Boot Test 提供了兩個註解來⽀持,測試時
只需在測試類的上⾯添加 @RunWith(SpringRunner.class) 和 @SpringBootTest 註解即可。
@RunWith(SpringRunner.class)
@SpringBootTest
public class HelloServiceTest {
@Resource
HelloService helloService;
@Test
public void sayHelloTest(){
helloService.sayHello();
}
}
同時在測試類中注⼊ HelloService,sayHelloTest 測試⽅法中調⽤ HelloService 的 sayHello() ⽅法,執⾏測
試⽅法後,就會發現在控制檯打印出了 Spring Boot 的啓動信息,說明在執⾏測試⽅法之前,Spring Boot 對
容器進⾏了初始化,輸出完啓動信息後會打印出以下信息:
hello service
證明測試服務成功,但是這種測試會稍顯麻煩,因爲控制檯打印了太多的東⻄,需要我們來仔細分辨,這⾥
有更優雅的解決⽅案,可以利⽤ OutputCapture 來判斷 System 是否輸出了我們想要的內容,添加
OutputCapture 改造如下。
import static org.assertj.core.api.Assertions.assertThat;
import org.springframework.boot.test.rule.OutputCapture;
@RunWith(SpringRunner.class)
@SpringBootTest
public class HelloServiceTest {
@Rule
public OutputCapture outputCapture = new OutputCapture();
@Resource
HelloService helloService;
@Test
public void sayHelloTest(){
helloService.sayHello();
assertThat(this.outputCapture.toString().contains("hello service")).isTrue
();
}
}
OutputCapture 是 Spring Boot 提供的⼀個測試類,它能捕獲 System.out 和 System.err 的輸出,我們可以利
⽤這個特性來判斷程序中的輸出是否執⾏。
這樣當輸出內容若是 "hello service",則測試⽤例執⾏成功;若不是,則會執⾏失敗,再也⽆需關注控制檯輸
出內容。GitChat
Web 測試
據統計現在開發的 Java 項⽬中 90% 以上都是 Web 項⽬,如何檢驗 Web 項⽬對外提供接⼝的準確性就變得
很重要。在以往的經歷中,我們常常會在瀏覽器中訪問⼀些特定的地址來進⾏測試,但如果涉及到⼀些⾮
get 請求就會變的稍微麻煩⼀些,有的讀者會使⽤ PostMan ⼯具或者⾃⼰寫⼀些 HTTP Post 請求來進⾏測
試,但終究不夠優雅⽅便。
Spring Boot Test 中有針對 Web 測試的解決⽅案:MockMvc,其實現了對 HTTP 請求的模擬,能夠直接使⽤
⽹絡的形式,轉換到 Controller 的調⽤,這樣可以使得測試速度更快、不依賴⽹絡環境,⽽且提供了⼀套驗
證的⼯具,這樣可以使得請求的驗證統⼀⽽且更⽅便。
接下來進⾏演示,⾸先在項⽬中添加 Web 依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
創建⼀個 HelloController 對外輸出⼀個 hello 的⽅法。
@RestController
public class HelloController {
@RequestMapping(name="/hello")
public String getHello() {
return "hello web";
}
}
創建 HelloWebTest 對我們上⾯創建的 web 接⼝ getHello() ⽅法進⾏測試。
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.pr
int;
@RunWith(SpringRunner.class)
@SpringBootTest
public class HelloWebTest {
private MockMvc mockMvc;
@Before
public void setUp() throws Exception {
mockMvc = MockMvcBuilders.standaloneSetup(new HelloController()).build();
}
@Test
public void testHello() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.post("/hello")
.accept(MediaType.APPLICATION_JSON_UTF8)).andDo(print());
}
}
- @Before 注意意味着在測試⽤例執⾏前需要執⾏的操作,這⾥是初始化需要建⽴的測試環境。
- MockMvcRequestBuilders.post 是指⽀持 post 請求,這⾥其實可以⽀持各種類型的請求,如 get 請求、 put 請求、patch 請求、delete 請求等。
- andDo(print())、andDo():添加 ResultHandler 結果處理器,print() 打印出請求和相應的內容。
控制檯輸出:
MockHttpServletRequest:
HTTP Method = POST
Request URI = /hello
Parameters = {}
Headers = {Accept=[application/json;charset=UTF-8]}
Body = <no character encoding set>
Session Attrs = {}
Handler:
Type = com.neo.web.HelloController
Method = public java.lang.String com.neo.web.HelloController.getUser()
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 200
Error message = null
Headers = {Content-Type=[application/json;charset=UTF-8], Content-Length
=[9]}
Content type = application/json;charset=UTF-8
Body = hello web
Forwarded URL = null
Redirected URL = null
Cookies = []
通過上⾯輸出的信息會發現,將整個請求的過程全部打印了出來,包括請求頭信息、請求參數、返回信息
等,根據打印的 Body 信息可以得知 HelloController 的 getHello() ⽅法測試成功。
但有時候我們並不想知道整個請求流程,只需要驗證返回的結果是否正確即可,可以做下⾯的改造:
@Test
public void testHello() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.post("/hello")
.accept(MediaType.APPLICATION_JSON_UTF8))
// .andDo(print())
.andExpect(content().string(equalTo("hello web")));
}
如果接⼝返回值是 "hello web" 測試執⾏成功,否則測試⽤例執⾏失敗。也⽀持驗證結果集中是否包含了特定
的字符串,這時可以使⽤ containsString() ⽅法來判斷。
.andExpect(content().string(containsString("hello")));;
⽀持直接將結果集轉換爲字符串輸出:
String mvcResult= mockMvc.perform(MockMvcRequestBuilders.get("/messages")).andRetu
rn().getResponse().getContentAsString();
System.out.println("Result === "+mvcResult);
⽀持在請求的時候傳遞參數:
@Test
public void testHelloMore() throws Exception {
final MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("id", "6");
params.add("hello", "world");
mockMvc.perform(
MockMvcRequestBuilders.post("/hello")
.params(params)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().isOk())
.andExpect(content().string(containsString("hello")));;
}
返回結果如果是 JSON 可以使⽤下⾯語法來判斷:
.andExpect(MockMvcResultMatchers.jsonPath("$.name").value("純潔的微笑"))
MockMvc 提供⼀組⼯具函數⽤來執⾏ Assert 判斷,這組⼯具使⽤函數的鏈式調⽤,允許將多個測試⽤例拼
接在⼀起,同時進⾏多個判斷。
- perform 構建⼀個請求,並且返回 ResultActions 實例,該實例則可以獲取到請求的返回內容。GitChat
- params 構建請求時候的參數,也⽀持 param(key,value) 的⽅式連續添加。
- contentType(MediaType.APPLICATION_JSON_UTF8) 代表發送端發送的數據格式。
- accept(MediaType.APPLICATION_JSON_UTF8) 代表客戶端希望接受的數據類型格式。
- mockMvc.perform() 建⽴ Web 請求。
- andExpect(...) 可以在 perform(...) 函數調⽤後多次調⽤,表示對多個條件的判斷。
- status().isOk() 判斷請求狀態是否返回 200。
- andReturn 該⽅法返回 MvcResult 對象,該對象可以獲取到返回的視圖名稱、返回的 Response 狀態、 獲取攔截請求的攔截器集合等。
JUnit 使⽤
JUnit 是針對 Java 語⾔的⼀個單元測試框架,它被認爲是迄今爲⽌所開發的最重要的第三⽅ Java 庫。 JUnit
的優點是整個測試過程⽆需⼈的參與、⽆需分析和判斷最終測試結果是否正確,⽽且可以很容易地⼀次性運
⾏多個測試。 JUnit 的最新版本爲 Junit 5,Spring Boot 默認集成的是 Junit 4。
以下爲 Junit 常⽤註解:
- @Test,把⼀個⽅法標記爲測試⽅法
- @Before,每⼀個測試⽅法執⾏前⾃動調⽤⼀次
- @After,每⼀個測試⽅法執⾏完⾃動調⽤⼀次
- @BeforeClass,所有測試⽅法執⾏前執⾏⼀次,在測試類還沒有實例化就已經被加載,因此⽤ static 修 飾
- @AfterClass,所有測試⽅法執⾏前執⾏⼀次,在測試類還沒有實例化就已經被加載,因此⽤ static 修 飾
- @Ignore,暫不執⾏該測試⽅法
- @RunWith 當⼀個類⽤ @RunWith 註釋或繼承⼀個⽤ @RunWith 註釋的類時,JUnit 將調⽤它所引⽤的
類來運⾏該類中的測試⽽不是開發者再去 JUnit 內部去構建它。我們在開發過程中使⽤這個特性看看。
創建測試類 JUnit4Test類:
public class JUnit4Test {
Calculation calculation = new Calculation();
int result; //測試結果
//在 JUnit 4 中使⽤ @Test 標註爲測試⽅法
@Test
//測試⽅法必須是 public void 的
public void testAdd() {
System.out.println("---testAdd開始測試---");
//每個⾥⾯只測⼀次,因爲 assertEquals ⼀旦測試發現錯誤就拋出異常,不再運⾏後續代碼
result = calculation.add(1, 2);
assertEquals(3, result);
System.out.println("---testAdd正常運⾏結束---");
GitChat
}
//⼜⼀個測試⽅法
//timeout 表示測試允許的執⾏時間毫秒數,expected 表示忽略哪些拋出的異常(不會因爲該異常導
致測試不通過)
@Test(timeout = 1, expected = NullPointerException.class)
public void testSub() {
System.out.println("---testSub開始測試---");
result = calculation.sub(3, 2);
assertEquals(1, result);
throw new NullPointerException();
//System.out.println("---testSub正常運⾏結束---");
}
//指示該[靜態⽅法]將在該類的[所有]測試⽅法執⾏之[前]執⾏
@BeforeClass
public static void beforeAll() {
System.out.println("||==BeforeClass==||");
System.out.println("||==通常在這個⽅法中加載資源==||");
}
//指示該[靜態⽅法]將在該類的[所有]測試⽅法執⾏之[後]執⾏
@AfterClass
public static void afterAll() {
System.out.println("||==AfterClass==||");
System.out.println("||==通常在這個⽅法中釋放資源==||");
}
//該[成員⽅法]在[每個]測試⽅法執⾏之[前]執⾏
@Before
public void beforeEvery() {
System.out.println("|==Before==|");
}
//該[成員⽅法]在[每個]測試⽅法執⾏之[後]執⾏
@After
public void afterEvery() {
System.out.println("|==After==|");
}
}
calculation 是⾃定義的計算器⼯具類,具體可以參考示例項⽬,執⾏測試類後,輸出:
||==BeforeClass==||
||==通常在這個⽅法中加載資源==||
|==Before==|
---testAdd開始測試---
---testAdd正常運⾏結束---
|==After==|
|==Before==|
---testSub開始測試---
|==After==|
||==AfterClass==||
||==通常在這個⽅法中釋放資源==||
對⽐上⾯的介紹可以清晰的瞭解每個註解的使⽤。
Assert 使⽤
Assert 翻譯爲中⽂爲“斷⾔”,使⽤過 JUnit 的讀者都熟知這個概念,它斷定某⼀個實際的運⾏值和預期想⼀
樣,否則就拋出異常 。Spring 對⽅法⼊參的檢測借⽤了這個概念,其提供的 Assert 類擁有衆多按規則對⽅
法⼊參進⾏斷⾔的⽅法,可以滿⾜⼤部分⽅法⼊參檢測的要求。
Spring Boot 也提供了斷⾔式的驗證,幫助我們在測試時驗證⽅法的返回結果。
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestAssert {
@Autowired
private UserService userService;
@Test
public void TestAssert(){
//驗證結果是否爲空
Assert.assertNotNull(userService.getUser());
//驗證結果是否相等
Assert.assertEquals("i am neo!", userService.getUser());
//驗證條件是否成⽴
Assert.assertFalse(1+1>3);
//驗證對象是否相等
Assert.assertSame(userService,userService);
int status=404;
//驗證結果集,提示
Assert.assertFalse("錯誤,正確的返回值爲200", status != 200);
String[] expectedOutput = {"apple", "mango", "grape"};
String[] methodOutput = {"apple", "mango", "grape1"};
//驗證數組是否相同
Assert.assertArrayEquals(expectedOutput, methodOutput);
}
}
通過上⾯使⽤的例⼦可以發現,使⽤ Assert 可以⾮常⽅便驗證測試返回結果,避免寫很多的 if/else 判斷,讓 代碼更加的優雅。
如何使⽤ assertThat
JUnit 4 學習 JMock,引⼊了 Hamcrest 匹配機制,使得程序員在編寫單元測試的 assert 語句時,可以具有
更強的可讀性,⽽且也更加靈活。Hamcrest 是⼀個測試的框架,它提供了⼀套通⽤的匹配符 Matcher,靈活
使⽤這些匹配符定義的規則,程序員可以更加精確的表達⾃⼰的測試思想,指定所想設定的測試條件。
斷⾔便是 Junit 中最⻓使⽤的語法之⼀,在⽂章內容開始使⽤了 assertThat 對 System 輸出的⽂本進⾏了判
斷,assertThat 其實是 JUnit 4 最新的語法糖,只使⽤ assertThat ⼀個斷⾔語句,結合 Hamcrest 提供的匹
配符,就可以替代之前所有斷⾔的使⽤⽅式。
assertThat 的基本語法如下:
assertThat( [value], [matcher statement] );
- value 是接下來想要測試的變量值;
- matcher statement 是使⽤ Hamcrest 匹配符來表達對前⾯變量所期望值的聲明,如果 value 值與 matcher statement 所表達的期望值相符,則測試成功,否則測試失敗。
⼀般匹配符
// allOf 匹配符表明如果接下來的所有條件必須都成⽴測試才通過,相當於“與”(&&)
assertThat( testedNumber, allOf( greaterThan(8), lessThan(16) ) );
// anyOf 匹配符表明如果接下來的所有條件只要有⼀個成⽴則測試通過,相當於“或”(||)
assertThat( testedNumber, anyOf( greaterThan(16), lessThan(8) ) );
// anything 匹配符表明⽆論什麼條件,永遠爲 true
assertThat( testedNumber, anything() );
// is 匹配符表明如果前⾯待測的 object 等於後⾯給出的 object,則測試通過
assertThat( testedString, is( "developerWorks" ) );
// not 匹配符和 is 匹配符正好相反,表明如果前⾯待測的 object 不等於後⾯給出的 object,則測試通
過
assertThat( testedString, not( "developerWorks" ) );
字符串相關匹配符
// containsString 匹配符表明如果測試的字符串 testedString 包含⼦字符串"developerWorks"則
測試通過
assertThat( testedString, containsString( "developerWorks" ) );
// endsWith 匹配符表明如果測試的字符串 testedString 以⼦字符串"developerWorks"結尾則測試通
過
assertThat( testedString, endsWith( "developerWorks" ) );
// startsWith 匹配符表明如果測試的字符串 testedString 以⼦字符串"developerWorks"開始則測試
通過
assertThat( testedString, startsWith( "developerWorks" ) );
// equalTo 匹配符表明如果測試的 testedValue 等於 expectedValue 則測試通過,equalTo 可以測
試數值之間的字
//符串之間和對象之間是否相等,相當於 Object 的 equals ⽅法
assertThat( testedValue, equalTo( expectedValue ) );
// equalToIgnoringCase 匹配符表明如果測試的字符串 testedString 在忽略⼤⼩寫的情況下等於
//"developerWorks"則測試通過
assertThat( testedString, equalToIgnoringCase( "developerWorks" ) );
// equalToIgnoringWhiteSpace 匹配符表明如果測試的字符串 testedString 在忽略頭尾的任意個空
格的情況下等
//於"developerWorks"則測試通過,注意,字符串中的空格不能被忽略
assertThat( testedString, equalToIgnoringWhiteSpace( "developerWorks" ) );
數值相關匹配符
// closeTo 匹配符表明如果所測試的浮點型數 testedDouble 在 20.0±0.5 範圍之內則測試通過
assertThat( testedDouble, closeTo( 20.0, 0.5 ) );
// greaterThan 匹配符表明如果所測試的數值 testedNumber ⼤於 16.0 則測試通過
assertThat( testedNumber, greaterThan(16.0) );
// lessThan 匹配符表明如果所測試的數值 testedNumber ⼩於 16.0 則測試通過
assertThat( testedNumber, lessThan (16.0) );
// greaterThanOrEqualTo 匹配符表明如果所測試的數值 testedNumber ⼤於等於 16.0 則測試通過
assertThat( testedNumber, greaterThanOrEqualTo (16.0) );
// lessThanOrEqualTo 匹配符表明如果所測試的數值 testedNumber ⼩於等於 16.0 則測試通過
assertThat( testedNumber, lessThanOrEqualTo (16.0) );
collection 相關匹配符
// hasEntry 匹配符表明如果測試的 Map 對象 mapObject 含有⼀個鍵值爲"key"對應元素值爲"value"
的 Entry 項則
//測試通過
assertThat( mapObject, hasEntry( "key", "value" ) );
// hasItem 匹配符表明如果測試的迭代對象 iterableObject 含有元素“element”項則測試通過
assertThat( iterableObject, hasItem ( "element" ) );
// hasKey 匹配符表明如果測試的 Map 對象 mapObject 含有鍵值“key”則測試通過
assertThat( mapObject, hasKey ( "key" ) );
// hasValue 匹配符表明如果測試的 Map 對象 mapObject 含有元素值“value”則測試通過
assertThat( mapObject, hasValue ( "key" ) );
具體使⽤可參考示例項⽬中 CalculationTest 的使⽤。
Junt 使⽤的⼏條建議:
- 測試⽅法上必須使⽤ @Test 進⾏修飾
- 測試⽅法必須使⽤ public void 進⾏修飾,不能帶任何的參數
- 新建⼀個源代碼⽬錄來存放我們的測試代碼,即將測試代碼和項⽬業務代碼分開
- 測試類所在的包名應該和被測試類所在的包名保持⼀致
- 測試單元中的每個⽅法必須可以獨⽴測試,測試⽅法間不能有任何的依賴
- 測試類使⽤ Test 作爲類名的後綴(不是必須)
- 測試⽅法使⽤ Test 作爲⽅法名的前綴(不是必須)
總結
Spring Boot 是⼀款⾃帶測試組件的開源軟件,Spring Boot Test 中內置了 7 種強⼤的測試⼯具,覆蓋了測試
中的⽅⽅⾯⾯,在實際應⽤中只需要導⼊ Spring Boot Test 既可讓項⽬具備各種測試功能。在微服務架構下
嚴格採⽤三層測試覆蓋,纔能有效保證項⽬質量。