junit 内部类测试_Springboot 使用单元测试
目標(biāo)
了解 單元測(cè)試的背景
了解如何 利用 springboot 實(shí)現(xiàn)接口的測(cè)試
了解如何 利用 mokito 做代碼的 mock
一、About 單元測(cè)試
單元測(cè)試其實(shí)是一種廉價(jià)的技術(shù),是由開(kāi)發(fā)者創(chuàng)建運(yùn)行測(cè)試代碼,用于對(duì)程序模塊(軟件設(shè)計(jì)的最小單位)進(jìn)行正確性檢驗(yàn)的一種做法。 而所謂的最小單元,就是指應(yīng)用的最小可測(cè)試部件。 在面向?qū)ο箢I(lǐng)域,最小單元對(duì)應(yīng)于類(lèi)的某個(gè)成員方法。
通常意義的單元測(cè)試會(huì)用于驗(yàn)證某場(chǎng)景、某條件下某方法的行為結(jié)果,舉個(gè)例子:
我想驗(yàn)證
? ?Equals 方法,在兩個(gè)對(duì)象類(lèi)型不一致時(shí)應(yīng)該返回 false
單元測(cè)試的初衷,是對(duì)各個(gè)相互獨(dú)立,互不影響的基本單元基線測(cè)試,以此來(lái)保證核心代碼的質(zhì)量。
每一段單元測(cè)試代碼,都一定會(huì)包含幾個(gè)部分:
Arrange 用于初始化一些被測(cè)試方法需要的參數(shù)或依賴的對(duì)象。
Act方法 用于調(diào)用被測(cè)方法進(jìn)行測(cè)試。
Assert?
? ? ? ?用于驗(yàn)證測(cè)試方法是否按期望執(zhí)行或者結(jié)果是否符合期望值
See !并不是很復(fù)雜,可是大多數(shù)開(kāi)發(fā)者并不喜歡做單元測(cè)試。 而且,有一個(gè)現(xiàn)象很有意思,水平越高的程序員,越不喜歡寫(xiě)測(cè)試代碼,why?
“ 因?yàn)閱卧獪y(cè)試,主要是用來(lái)防低級(jí)程序員挖坑的啊 ”
這句話不是我說(shuō)的,但卻代表了相當(dāng)一部分程序員的心聲..
那么,單元測(cè)試到底要不要做,并不是本文要討論的問(wèn)題。 建議大家閱讀下 《單元測(cè)試之道-Java版本》 (程序員修煉三部曲系列)這邊書(shū),看完后再做出自己的理解。
為了測(cè)試一座橋梁,不應(yīng)該只在晴朗的天氣,開(kāi)一輛汽車(chē)從橋中間穿過(guò),就認(rèn)為已經(jīng)完成了對(duì)橋梁的測(cè)試
二、About Junit
接下來(lái),要說(shuō)一說(shuō) Junit框架,這個(gè)是最流行的Java 單元測(cè)試框架。
Junit 創(chuàng)建者是 Kent Beck 和?Erich Gamma,自其出現(xiàn)以來(lái),Junit 生態(tài)圈已經(jīng)非常龐大。 大量的應(yīng)用程序、開(kāi)發(fā)框架都以 Junit 作為標(biāo)準(zhǔn)的的基礎(chǔ)測(cè)試組件,這當(dāng)然也包括 Spring系列的框架。
一個(gè)典型的Junit單元測(cè)試類(lèi):
class StandardTests {
? ?@BeforeClass
? ?static void initAll() {
? ?}
? ?@Before
? ?void init() {
? ?}
? ?@Test
? ?void justTest() {
? ?...
? ?assertTrue(...)
? ?}
? ?@After
? ?void tearDown() {
? ?}
? ?@AfterClass
? ?static void tearDownAll() {
? ?}
}
說(shuō)明
| @BeforeClass | 在當(dāng)前類(lèi)測(cè)試之前執(zhí)行 |
| @Before | 在每個(gè)測(cè)試方法之前執(zhí)行 |
| @Test | 聲明測(cè)試方法 |
| @After | 在每個(gè)測(cè)試方法之后執(zhí)行 |
| @AfterClass | 在當(dāng)前類(lèi)測(cè)試之后執(zhí)行 |
這幾個(gè)注解還是比較容易理解的,需要注意的只是 @BeforeClass 和 @Before,前者是一個(gè)靜態(tài)方法, 會(huì)在整個(gè)測(cè)試用例類(lèi)開(kāi)始前執(zhí)行,僅一次;
?而后者則是在方法測(cè)試之前觸發(fā),可能會(huì)執(zhí)行多次。
為了更清晰的理解Junit 是怎么運(yùn)作,下面展示一個(gè)源碼片段:
? ?public void runBare() throws Throwable {
? ? ? ?Throwable exception = null;
? ? ? ?setUp();
? ? ? ?try {
? ? ? ? ? ?runTest();
? ? ? ?} catch (Throwable running) {
? ? ? ? ? ?exception = running;
? ? ? ?} finally {
? ? ? ? ? ?try {
? ? ? ? ? ? ? ?tearDown();
? ? ? ? ? ?} catch (Throwable tearingDown) {
? ? ? ? ? ? ? ?if (exception == null) exception = tearingDown;
? ? ? ? ? ?}
? ? ? ?}
? ? ? ?if (exception != null) throw exception;
? ?}
這是早期版本的TestCase類(lèi)其中的一段實(shí)現(xiàn),與我們所說(shuō)的思路是基本一致的! 然而,基于注解的實(shí)現(xiàn)是由 Junit4提供的,在有興趣的話可以深入看看源碼。
關(guān)鍵詞
TestCase、JUnit4TestAdapter、BlockJUnit4ClassRunner
三、SpringBoot-單元測(cè)試
SpringBoot 提供了 spring-boot-starter-test 用于實(shí)現(xiàn)單元測(cè)試。
項(xiàng)目依賴
org.springframework.boot
spring-boot-starter-test
${spring-boot.version}
測(cè)試樣例
@RunWith(SpringRunner.class)
@SpringBootTest(classes = DemoBoot.class)
public class RestApiTest {
? ?private MockMvc mockMvc;
? ?private ObjectMapper mapper = new ObjectMapper();
? ?@Autowired
? ?private WebApplicationContext context;
? ?@Autowired
? ?private RestDataManager dataManager;
? ?private static final String CUSTOMER = "LiLei";
? ?private Pet polly;
? ?private Pet badboy;
? ?@Before
? ?public void setupMockMvc() throws Exception {
? ? ? ?mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
? ? ? ?initData();
? ?}
? ?private void initData() {
? ? ? ?// 清除原有寵物信息
? ? ? ?dataManager.clearPets(CUSTOMER);
? ? ? ?// 添加新的寵物信息
? ? ? ?polly = new Pet();
? ? ? ?polly.setType("Bird");
? ? ? ?polly.setName("Polly");
? ? ? ?polly.setDescription("the rapid speaker");
? ? ? ?dataManager.addPet(CUSTOMER, polly);
? ? ? ?badboy = new Pet();
? ? ? ?badboy.setType("Dog");
? ? ? ?badboy.setName("BadBoy");
? ? ? ?polly.setDescription("the monster");
? ? ? ?dataManager.addPet(CUSTOMER, badboy);
? ?}
? ?@Test
? ?public void testGet() throws Exception {
? ? ? ?mockMvc.perform(MockMvcRequestBuilders.get("/rest/pets/{customer}/{petId}",
? ? ? ? ? ? ? ?CUSTOMER, polly.getPetId()))
? ? ? ? ? ? ? ?.andExpect(MockMvcResultMatchers.status().isOk())
? ? ? ? ? ? ? ?.andExpect(MockMvcResultMatchers.content()
? ? ? ? ? ? ? ? ? ? ? .contentType(MediaType.APPLICATION_JSON_UTF8))
? ? ? ? ? ? ? ?.andExpect(MockMvcResultMatchers.content()
? ? ? ? ? ? ? ? ? ? ? .json(mapper.writeValueAsString(polly)))
? ? ? ? ? ? ? ?.andDo(MockMvcResultHandlers.print());
? ?}
}
說(shuō)明
SpringRunner繼承于SpringJUnit4ClassRunner,這是Spring框架基于Junit實(shí)現(xiàn)的基礎(chǔ)類(lèi)。
如果還記得前面提到的 BlockJUnit4ClassRunner,應(yīng)該不難猜到,Spring 的實(shí)現(xiàn)類(lèi)集成了該類(lèi)。
那么,SpringRunner 做了什么? 什么也沒(méi)有,只是一個(gè)名稱的修正而已(論命名的重要性)
@SpringBootTest的作用
其代碼注釋如下:
Annotation that can be specified on a test class that runs Spring Boot based tests.
Provides the following features over and above the regular Spring TestContext Framework:
1. Uses SpringBootContextLoader as the default ContextLoader when no specific @ContextConfiguration(loader=...) is defined.
2. Automatically searches for a @SpringBootConfiguration when nested @Configuration is not used, and no explicit classes are specified.
3. Allows custom Environment properties to be defined using the properties attribute.
4. Provides support for different webEnvironment modes, including the ability to start a fully running container listening on a defined or random port.
5. Registers a TestRestTemplate bean for use in web tests that are using a fully running container.
要點(diǎn)
默認(rèn)會(huì)使用SpringBootContextLoader類(lèi)用于上下文加載, 這個(gè)類(lèi)將會(huì)使用所配置的SpringBootApplication實(shí)體類(lèi)作為入口,加載配置并初始化Spring上下文環(huán)境;
可以支持自定義的配置,通過(guò) Environment 屬性設(shè)置;
支持不同的 web 環(huán)境模式,可以是固定端口、隨機(jī)端口、無(wú)端口幾種模式。
關(guān)鍵詞
SpringRunner、SpringBootTest、SpringBootContextLoader
四、Mock測(cè)試
Mock 測(cè)試的使用場(chǎng)景在于,被測(cè)試模塊(方法)依賴于外部系統(tǒng)(web服務(wù)、中間件或是數(shù)據(jù)庫(kù))。
我們需要提供一種快速驗(yàn)證本地實(shí)現(xiàn)邏輯的策略,那就是 Mock,也稱為打樁。
如上圖,A 模塊依賴于 B 模塊,在 B 模塊不可達(dá)的時(shí)候,我們對(duì) 依賴接口進(jìn)行了 Mock。這樣在執(zhí)行測(cè)試時(shí),不需要真實(shí)的 B 模塊便可完成測(cè)試。
下面我們要用到的 Mock 組件叫 Mockito
springboot-starter-test 自帶了對(duì)于 mockito 的依賴,下面看一段代碼:
? ?@Before
? ?public void setupMockMvc() throws Exception {
? ? ? ?// 啟用mock
? ?@Before
? ?public void setupMockMvc() throws Exception {
? ? ? ?// 啟用mock
? ? ? ?MockitoAnnotations.initMocks(this);
? ? ? ?polly = new Pet();
? ? ? ?polly.setType("Bird");
? ? ? ?polly.setName("Polly");
? ? ? ?polly.setDescription("the rapid speaker");
? ? ? ?lilei = new Customer();
? ? ? ?lilei.setName(CUSTOMER);
? ? ? ?// 設(shè)置mock接口
? ? ? ?Mockito.when(dataManager.getPets(Mockito.isA(String.class))).thenReturn(Arrays.asList(polly));
? ? ? ?Mockito.when(dataManager.getCustomer(Mockito.isA(String.class))).thenReturn(lilei);
? ? ? ?// 使用standaloneSetup,指定controller
? ? ? ?// 由于不通過(guò)webappliationContext初始化,許多配置不會(huì)自動(dòng)完成,此外bean的初始化方法也不會(huì)執(zhí)行
? ? ? ?mockMvc = MockMvcBuilders.standaloneSetup(controller)
? ? ? ? ? ? ? ?.setMessageConverters(new MappingJackson2HttpMessageConverter()).build();
? ?}
? ? ? ?polly = new Pet();
? ? ? ?polly.setType("Bird");
? ? ? ?polly.setName("Polly");
? ? ? ?polly.setDescription("the rapid speaker");
? ? ? ?lilei = new Customer();
? ? ? ?lilei.setName(CUSTOMER);
? ? ? ?// 設(shè)置mock接口
? ? ? ?Mockito.when(dataManager.getPets(Mockito.isA(String.class))).thenReturn(Arrays.asList(polly));
? ? ? ?Mockito.when(dataManager.getCustomer(Mockito.isA(String.class))).thenReturn(lilei);
? ? ? ?// 使用standaloneSetup,指定controller
? ? ? ?// 由于不通過(guò)webappliationContext初始化,許多配置不會(huì)自動(dòng)完成,此外bean的初始化方法也不會(huì)執(zhí)行
? ? ? ?mockMvc = MockMvcBuilders.standaloneSetup(controller)
? ? ? ? ? ? ? ?.setMessageConverters(new MappingJackson2HttpMessageConverter()).build();
? ?}
看到了嗎,利用 Mockito 可以實(shí)現(xiàn)你想要的 Mock效果,如下:
Mockito.when( somemethod ).thenReturn( some thing to return);
然而,在進(jìn)行 mock 方法時(shí),需要使用 standaloneSetup 的模式, 否則 mockito 無(wú)法工作。
mockMvc = MockMvcBuilders.standaloneSetup(controller)..
關(guān)鍵詞
Mockito、MockMvcBuilders
五、最后
細(xì)心的讀者會(huì)發(fā)現(xiàn),前面講了單元測(cè)試的對(duì)象,是指軟件設(shè)計(jì)的最小單位(方法),可是為什么到了 SpringBoot 的部分卻都是對(duì)于API(Controller層)的測(cè)試呢??
到底我們的單元測(cè)試應(yīng)該針對(duì)內(nèi)部實(shí)現(xiàn)的某個(gè)單元,比如 DAO/Service方法,還是針對(duì)接口(API Interface)?
筆者認(rèn)為,這點(diǎn)并沒(méi)有絕對(duì)的好壞之分,關(guān)鍵在于取舍。?
單元測(cè)試是軟件工程領(lǐng)域的概念,而軟件項(xiàng)目是分很多種類(lèi)型的,比如在早期的軟件工程中,就有不少的基于C/S架構(gòu)的程序,這類(lèi)程序的體積相對(duì)龐大,往往需要對(duì)大量模塊級(jí)的方法進(jìn)行單元測(cè)試;
現(xiàn)如今的微服務(wù)體系架構(gòu)中,對(duì)于各個(gè)子系統(tǒng)來(lái)說(shuō),API(作為契約)是必須進(jìn)行測(cè)試的。 對(duì)于某服務(wù)的單元測(cè)試,選擇 Controller 還是 Service層,取決于你的成本效益考慮,而目前來(lái)看,結(jié)合敏捷化的 TDD實(shí)踐、 通過(guò)單元測(cè)試進(jìn)行 API測(cè)試 已經(jīng)是一種主流做法。
兩年嘔心瀝血的文章:「面試題」「基礎(chǔ)」「進(jìn)階」這里全都有!
300多篇原創(chuàng)技術(shù)文章加入交流群學(xué)習(xí)海量視頻資源精美腦圖面試題長(zhǎng)按掃碼可關(guān)注獲取?
在看和分享對(duì)我非常重要!
總結(jié)
以上是生活随笔為你收集整理的junit 内部类测试_Springboot 使用单元测试的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: python中用来占位_自定义占位符,如
- 下一篇: springboot启动没反应_新特性: