springboot-單元測試梳理

springboot-單元測試梳理

今天主要是梳理springboot單元測試,基於MockMvc進行實戰演示。

1. sringboot的測試類庫

springboot本身就集成並且提供了非常多的實用工具以及註解。幫助開發者更加專注業務開發。

在這裏主要介紹一下,springboot提供的測試方面的工具以及註解。主要包括兩個模塊:

  • spring-boot-test: 支持測試的核心內容。

  • spring-boot-test-autoconfigure:支持測試的自動化配置

我們只需要引入上面的依賴,springboot就會幫我們引入測試模塊。我們只需要使用註解。就能輕鬆完成調用。

比如引入的測試類庫有:

  • Junit: java單元測試的標準類庫。
  • Spring Test & Spring Boot Test: Springboot功能集成化測試支持。
  • AssertJ: 一個輕量級的斷言類庫
  • Hamcrest: 一個對象匹配器類庫
  • Mockito: 一個java Mock測試框架

2. Junit 4常用註解

  • @BeforeClass: 針對所有的測試,只執行一次,且方法還必須是static void

  • @Before: 初始化方法,執行當前測試類的每個測試方法前執行

  • @Test: 測試方法,在這裏執行測試,也可以測試期望異常和超時時間(可以查看註解源碼,查看使用屬性)

  • @After: 釋放資源,可以執行當前測試類的每一個測試方法後執行

  • @AfterClass: 針對所有的測試,只執行一次,且方法還必須是static void

  • @Ignore: 忽略此測試方法

  • @Runwith: 可以更改測試運行器

    一個單元測試類執行的順序:

    @BeforeClass -> @Before -> @Test -> @After -> @AfterClass

    每個一個測試方法執行的調用順序:

    @Before -> @Test -> @After

3. @RunWith @SpringBootTest

3.1 @RunWith

在JUnit中有很多個Runner,他們負責調用你的測試代碼,每一個Runner都有各自的特殊功能,你要根據需要選擇不同的Runner來運行你的測試代碼。在測試的時候,不指定使用的是默認的Runner。只能測試普通的java測試。不涉及spring web項目

在springboot中,SpringBoot 2.X 默認使用Junit4 我們使用註解@RunWith(SpringRunner.class)進行指定

public final class SpringRunner extends SpringJUnit4ClassRunner {
	public SpringRunner(Class<?> clazz) throws InitializationError {
		super(clazz);
	}
}

可以看到源碼中,SpringRunner繼承的SpringJUnit4ClassRunner

3.2 @SpringBootTest

@SpringBootTest註解是SpringBoot自1.4.0版本開始引入的一個用於測試的註解。他是屬於SpringBoot的註解。

在進行web 環境測試的時候,我通常使用@SpringBootTest(classes = SpringbootApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)

其中classes = SpringbootApplication.class是指定啓動類、webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT是指定web環境

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@BootstrapWith(SpringBootTestContextBootstrapper.class)
public @interface SpringBootTest {
	@AliasFor("properties")
	String[] value() default {};
	@AliasFor("value")
	String[] properties() default {};
	Class<?>[] classes() default {};
	WebEnvironment webEnvironment() default WebEnvironment.MOCK;
	enum WebEnvironment {
		MOCK(false),
		RANDOM_PORT(true),
		DEFINED_PORT(true),
		NONE(false);
		private final boolean embedded;
		WebEnvironment(boolean embedded) {
			this.embedded = embedded;
		}
		public boolean isEmbedded() {
			return this.embedded;
		}
	}
}

查看源碼發現:

SpringBootTest.WebEnvironment中一共有 4 個枚舉值

  • MOCK: MOCK 是用的比較多的一個屬性。它會判斷,當你使用 servlet API 時,則使用模擬 servlet 環境創建 WebApplicationContext;如果是使用 WebFlux,則使用 ReactiveWebApplicationContext;否則,使用常規的 ApplicationContext。
  • RANDOM_PORT: 創建一個 web 應用程序上下文,可以是 reactive,也可以是 servlet。它將隨機起一個端口並監聽。到底是 reactive 還是 Servlet,它會自己根據上下文來判斷
  • DEFINED_PORT: 創建一個(reactive) web 應用程序上下文,使用默認端口。針對這個屬性,網上翻譯的解讀比較多,錯誤的也多。
  • NONE: 它不會指定 SpringApplication.setWebApplicationType。以非 Web 環境來運行。也就是非 Servlet 和 Reactive 環境

還有不少人的文章說,SpringBootTest.WebEnvironment.RANDOM_PORT 必須或通常和 @LocalServerPort 一起使用。我看源碼的註釋,解釋並沒有說跟@LocalServerPort 一起使用。我本身也不用。

爲了減少篇幅,文中源碼註釋我都做了刪除處理。感興趣可以自己去查看。

4. MockMVC介紹

MockMvc是由spring-test包提供,實現了對Http請求的模擬,能夠直接使用網絡的形勢,轉換到Controller的調用,是的測試速度快,不依賴網絡環境。同時提供了一套驗證的工具,結果的驗證十分方便。
基於RESTful 風格的springMVC測試。可以測試整個SpringMVC流程。從URL請求到Controller控制層的測試。

4.1 MockMvc

  • 作用: 服務器端SpringMVC測試的主入口點
  • 創建: 通過MockMVCBuilders的靜態方法建造MockMVCBuilder,MockMvcMockMVCBuilder構造
  • 調用: perform(RequestBuilder rb),執行一個RequestBuilder請求,會自動執行SpringMVC的流程並映射到相應的控制器執行處理,該方法的返回值是一個ResultActions。

4.2 MockMVCBuilders

  • 作用: 負責創建MockMVCBuilder對象
  • 創建: 有兩種創建方式
    • standaloneSetup(Object… controllers): 通過參數指定一組控制器,這樣就不需要從上下文獲取了。
    • webAppContextSetup(WebApplicationContext wac):指定WebApplicationContext,將會從該上下文獲取相應的控制器並得到相應的MockMv

4.3 MockMVCBuilder

  • 作用: MockMVCBuilder使用構造者模式來構建MockMvc的構造器
  • 創建: 主要的實現:StandaloneMockMvcBuilderDefaultMockMvcBuilder
  • 也可以直接使用靜態工廠MockMvcBuilders創建即可,不需要直接使用上面兩個實現類

4.4 MockMvcRequestBuilders

  • 作用: 用來構建Request請求的。
  • 創建: 其主要有兩個子類MockHttpServletRequestBuilderMockMultipartHttpServletRequestBuilder(如文件上傳使用),即用來Mock客戶端請求需要的所有數據。

4.5 ResultActions

方法介紹:

  • andExpect:添加ResultMatcher驗證規則,驗證控制器執行完成後結果是否正確。
  • andDo:添加ResultHandler結果處理器,比如調試時打印結果到控制檯。
  • andReturn:最後返回相應的MvcResult;然後進行自定義驗證/進行下一步的異步處理。
  • MockMvcResultMatchers
    • 用來匹配執行完請求後的**結果驗證。
    • 果匹配失敗將拋出相應的異常。
    • 包含了很多驗證API方法。
  • MockMvcResultHandlers
    • 結果處理器,表示要對結果做點什麼事情。
    • 比如此處使用MockMvcResultHandlers.print()輸出整個響應結果信息。

4.6 MvcResult

  • 作用: 單元測試執行結果,可以針對執行結果進行自定義驗證邏輯。採用斷言驗證數據。

5. 實戰

完整的代碼github路徑: [email protected]:agreewangshuai/marsha_springboot.git(一個完整的springboot骨架集成統一結果返回、統一異常處理、統一日誌框架以及單元測試、swagger)

添加Gradle依賴

testImplementation('org.springframework.boot:spring-boot-starter-test')

model

@ApiModel(value = "HelloWorld參數類")
public class HelloWorld {
	@ApiModelProperty(value = "要說內容")
	@NotNull(message = "不能爲空或者\"\"")
	private String say;
	public String getSay() {
		return say;
	}
	public void setSay(String say) {
		this.say = say;
	}	
}

controller

@RestController
@RequestMapping("/agree")
@Api(value = "類描述",tags = {"hello demo功能"})
public class HelloWorldController {

	@Autowired
	HelloWorldService helloWorldService;
	
	@ApiOperation(value = "返回say hello",notes = "方法的具體描述信息")
	@GetMapping("/helloworld")
	public ResponseData getHello(@RequestBody  @Valid HelloWorld helloWorld) {
		String say = helloWorld.getSay();
		String sayhello = helloWorldService.sayhello(say);
		return new ResponseData().ok().data(sayhello);
	}
	
}

service

public interface HelloWorldService {
	/**
	 * @param say
	 * @return
	 */
   String sayhello(String say);
}

serviceImpl

@Service
public class HelloWorldServiceImpl implements HelloWorldService{
	@Override
	public String sayhello(String say) {
		return say;
	}
}

Test

@RunWith(SpringRunner.class)
@SpringBootTest(classes = SpringbootApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class HelloWorldControllerTest {
	public static final Logger log = LoggerFactory.getLogger(HelloWorldControllerTest.class);

	@Autowired
	private WebApplicationContext ctx;
	private MockMvc mvc;

	@Before
	public void before() {
		mvc = MockMvcBuilders.webAppContextSetup(ctx).build();
	}
	@Test
	public void getHelloTest() throws Exception {
		RequestBuilder request = null;
		MvcResult mvcResult = null;
		HelloWorld helloWorld = new HelloWorld();
		// helloWorld.setSay("say agree");
        /**
        * 添加請求參數,此處示例傳參格式爲json
        * new JsonHelper().objectToJson(helloWorld)是我自己寫的一個util類,將model轉爲json
        * 嫌麻煩也可以直接寫成json格式:
        *  content("{\"say\":\"hello world\"}")
        */
		request = MockMvcRequestBuilders.get("/agree/helloworld").content(new JsonHelper().objectToJson(helloWorld))
				.contentType(MediaType.APPLICATION_JSON);
		mvcResult = mvc.perform(request).andExpect(status().isOk()).andReturn();
		// 獲取數據
		JSONObject jsonObject = new JSONObject(mvcResult.getResponse().getContentAsString());
		log.info("輸出返回結果:{}", jsonObject);
		// 加斷言,判斷屬性值的問題。
		Assert.assertNotNull(jsonObject.get("code"));
		Assert.assertEquals(jsonObject.get("code"), "0");
		Assert.assertNotNull(jsonObject.get("message"));
		Assert.assertEquals(jsonObject.get("message"), "操作成功");
		Assert.assertNotNull(jsonObject.get("data"));
	}
}

小結:

本文單元測試主要描述了springboot單元測試的MockMvc。主要針對controller層的單元測試方法的介紹。目前暫時用的功能就這麼多。後續有新的研究心得,會持續更新。如有紕漏,歡迎指正。謝謝

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章