SpringBoot單元測(cè)試使用@Test沒(méi)有run方法的解決方案
SpringBoot單元測(cè)試使用@Test沒(méi)有run方法
吐了!一個(gè)關(guān)鍵字,糾錯(cuò)兩小時(shí),看了十幾篇博客。。。。最后重新建測(cè)試類發(fā)現(xiàn)@Test又有用,結(jié)果發(fā)現(xiàn)是因?yàn)槟J(rèn)的Tests測(cè)試類沒(méi)有public關(guān)鍵字!
這個(gè)破錯(cuò)改了兩小時(shí)。。。
==后續(xù)來(lái)了:==
原因找到了
建項(xiàng)目的時(shí)候是默認(rèn)的2.3.0,所以默認(rèn)創(chuàng)建的類結(jié)構(gòu)應(yīng)該是2.3.0版本的。
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.0.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent>
emmm。之前因?yàn)楦某闪?.1.7.RELEASE,版本不同,項(xiàng)目的結(jié)構(gòu)也不同。
現(xiàn)在用回2.3.0RELEASE
是可以正常跑的。。。而且也沒(méi)有了@RunWith注解
SpringBoot寫(xiě)單元測(cè)試遇到的坑
近期,項(xiàng)目需要寫(xiě)單元測(cè)試。我著手的項(xiàng)目是用SpringBoot寫(xiě)的。所以就簡(jiǎn)單的研究了一下如何使用。在使用中遇到不少問(wèn)題,不得已換了一種方式寫(xiě)測(cè)試用例,寫(xiě)完之后總感覺(jué)不太爽。今天在Spring官網(wǎng)上學(xué)一個(gè)新的用法,發(fā)現(xiàn)這種測(cè)試方法使用后沒(méi)有問(wèn)題。所以來(lái)寫(xiě)一點(diǎn)筆記。
SpringBoot怎么寫(xiě)單元測(cè)試
SpringBoot提供注解的方式編寫(xiě)單元測(cè)試,可以使用SpringBootTest注解來(lái)標(biāo)示測(cè)試類。
@RunWith(SpringRunner.class) @SpringBootTest public class SpringBootTest{ @Test public void method(){ } }
這樣寫(xiě)只能解決沒(méi)有一些配置文件的測(cè)試邏輯,比如沒(méi)有數(shù)據(jù)庫(kù)配置、數(shù)據(jù)庫(kù)連接池配置等。如果有這些配置,你就需要這樣寫(xiě)了。
@RunWith(SpringRunner.class) @SpringBootTest(classes = Application.class) @Test public void method(){ }
這樣就可以正常運(yùn)行了。
測(cè)試controller類。使用了Mock,網(wǎng)上大多流傳的是下面這種方法,添加@WebAppConfiguration,使用MockMvc去進(jìn)行單元測(cè)試,但是我的項(xiàng)目如下使用就出現(xiàn)了問(wèn)題,執(zhí)行的時(shí)候找不到Controller類,網(wǎng)上百度了各種方法都不管用。都會(huì)報(bào) no bean of 'controller' type found錯(cuò)誤。
@RunWith(SpringRunner.class) @SpringBootTest(classes = Application.class) @WebAppConfiguration public class ControllerTest { private MockMvc mockMvc; @Autowired private WebApplicationContext wac; @Before // 在測(cè)試開(kāi)始前初始化工作 public void setup() { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); } @Test public void getMessageTest() throws Exception { MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/test/getMessage")) .andDo(MockMvcResultHandlers.print()).andReturn(); int status = mvcResult.getResponse().getStatus(); String content = mvcResult.getResponse().getContentAsString(); Assert.assertTrue("success", status == 200); Assert.assertFalse("failed", status != 200); System.out.println("content" + content); }
后來(lái)?yè)Q了一種方式直接new個(gè)controller。測(cè)試運(yùn)行后不報(bào)no bean of 'controller' type found錯(cuò)誤了,但是在controller中使用的service報(bào)了空指針異常NPE,傳遞性就很明顯了,controller是new的一個(gè)對(duì)象,所以注解不起作用,service就為null。
最后通過(guò)使用@AutoConfigureMockMvc+@MockBean的方式可以實(shí)現(xiàn)簡(jiǎn)單的單元測(cè)試,并且不會(huì)對(duì)數(shù)據(jù)產(chǎn)生影響,且不會(huì)對(duì)數(shù)據(jù)庫(kù)產(chǎn)生影響。
@RunWith(SpringRunner.class) @SpringBootTest(classes = Application.class) @AutoConfigureMockMvc public class ImkfMessageReportControllerTest { /** * 初始化MockMvc */ @Autowired private MockMvc mvc; /** * 測(cè)試的controller */ @MockBean private UserController userController; @Test public void getUserListTest() throws Exception { MvcResult mvcResult = mvc.perform(MockMvcRequestBuilders.get("/user/getUserList")) .andExpect(MockMvcResultMatchers.status().isOk()) .andDo(MockMvcResultHandlers.print()) .andReturn(); String content = mvcResult.getResponse().getContentAsString(); System.out.println("content" + content); }
SpringBoot使用Mockito進(jìn)行單元測(cè)試
上面是使用MockMvc,雖然能夠驗(yàn)證短鏈接甚至service代碼邏輯的正確性,能夠正常測(cè)試接口的問(wèn)題。但是缺點(diǎn)也不少,比如,覆蓋率并沒(méi)有提升。Mockito是一個(gè)非常好用的單元測(cè)試工具,它的實(shí)現(xiàn)原理是繼承要Mock的類,將所有的公有方法進(jìn)行重寫(xiě)
@RunWith(MockitoJUnitRunner.class) public class UserServiceTest { @Mock private UserMapper userMapper; @InjectMocks private UserService userService; @Test public void saveTest() throws Exception { User user = new User(); user.setUserName(Long.valueOf("springBoot")); when(userMapper.insert(user)).thenReturn(user); int num = userService.save(user); Assert.assertEquals("success", 1, 1); } }
使用RunwWith(MockitoJUnitRunner.class)(也可以使用SpringBootRunner.class)來(lái)進(jìn)行mocktio測(cè)試,注解@Mock標(biāo)記一個(gè)類或者接口是需要被mock的,在Mock的過(guò)程中就相當(dāng)于@Resource,但是注意一點(diǎn)是Mock是繼承or實(shí)現(xiàn)了Mock的類,所以Mock出來(lái)的方法,全是null或者返回值為null。@InjectMocks將所有的mock對(duì)象都放入需要測(cè)試的類的對(duì)象中。在上面的saveTest方法里面調(diào)用到UserMapper.insert(),那么需要對(duì)UserMapper.insert()進(jìn)行打樁,設(shè)置預(yù)期返回值。
打樁的時(shí)候需要注意:傳遞的參數(shù)(如果有)必須為調(diào)用時(shí)的同一個(gè)對(duì)象或者相同值,如果傳入的參數(shù)是一個(gè)對(duì)象,那么需要對(duì)這個(gè)對(duì)象進(jìn)行打樁,再打樁這個(gè)方法。比如,when(userMapper.insert(user)).thenReturn(rUser),插入一個(gè)user對(duì)象,如果user插入之前要進(jìn)行校驗(yàn)或者其他操作,需要對(duì)這個(gè)對(duì)象進(jìn)行打樁(當(dāng)然pojp對(duì)象可以直接new)。
如果插入的對(duì)象非常復(fù)雜,用構(gòu)造方法來(lái)構(gòu)造一個(gè)空對(duì)象,或者構(gòu)造方法所用的對(duì)象不能直接構(gòu)造,但是沒(méi)有public的方法來(lái)設(shè)置值,該如何解決這個(gè)問(wèn)題?我們知道一個(gè)對(duì)象的類可能有g(shù)et方法(不一定是get,但是只要我們想獲取這個(gè)對(duì)象中的參數(shù),那么就有public的方法獲?。覀兛梢酝ㄟ^(guò)Mock這個(gè)對(duì)象,在將要測(cè)試的方法體內(nèi),如果某行調(diào)用了這個(gè)對(duì)象的任意方法(toString()、equals()、get()),我們都可以以相同的參數(shù)(如果遇到參數(shù)未知可以用any(),一般都能知道)進(jìn)行打樁后設(shè)置返回值,這樣就能通過(guò)參數(shù)校驗(yàn)等環(huán)節(jié),執(zhí)行后面的代碼邏輯,同時(shí)能夠提高覆蓋率,偽代碼如下。
@Mock private User user; when(user.get(eq("userName"))).thenReturn("testAdmin"); when(user.get(eq("seq"))).thenReturn(4); when(user.get(eq("password"))).thenReturn("123456"); when(user.get(eq("u_id"))).thenReturn("654321");
通過(guò)真實(shí)測(cè)試用例測(cè)試代碼
Mockito測(cè)試需要設(shè)置參數(shù)和預(yù)期返回值,在方法體中遇到的所有未知對(duì)象(除了方法體中new的對(duì)象不需要)都需要進(jìn)行模擬,但是在SpringBoot代碼剛剛完成的初期時(shí),跟想模擬真實(shí)場(chǎng)景下進(jìn)行單元測(cè)試代碼問(wèn)題or配置問(wèn)題,那么通過(guò)自動(dòng)注入的方式引入對(duì)象是一種更好的選擇。
ProviderServiceImpl.java -----服務(wù)類 import com.alibaba.dubbo.config.annotation.Service; import com.example.demo.service.DemoService; @Service public class ProviderServiceImpl implements DemoService { @Override public String sayHello(String name) { return "hello " + name + " !"; } } DemoApplicationTests.java -----測(cè)試類 @RunWith(SpringRunner.class) @SpringBootTest public class DemoApplicationTests { @Resource DemoService providerService; @Test public void contextLoads() { String result = providerService.sayHello("Spring Boot Test"); System.out.println("result is "+result); Assert.assertEquals("success","hello Spring Boot Test !" ,result); } }
這里需要注意的是DemoApplicationTests 需要跟啟動(dòng)類main在同一級(jí)目錄下,如果跟mvc在同一層可以會(huì)出現(xiàn)部分bean掃描不到的情況。如目錄層級(jí)很深或者程序啟動(dòng)比較慢的話,可以去掉SpringBootTest(去掉后就不啟動(dòng)程序,只會(huì)運(yùn)行該測(cè)試),運(yùn)行一下,測(cè)試結(jié)果如下:
通過(guò)這種注解的方式,可以測(cè)試dubbo連接(Refernce注解),可以測(cè)試controller層,redis數(shù)據(jù),mysql數(shù)據(jù),都會(huì)真實(shí)模擬,你只需要在注入你需要測(cè)試的類,在類的入口傳入測(cè)試參數(shù),在測(cè)試過(guò)程中,最好采用debug的方式,這樣你可以看到每一步的數(shù)據(jù),也便于定位程序的問(wèn)題(當(dāng)然也可以出現(xiàn)問(wèn)題時(shí)使用debug)。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
基于Protobuf動(dòng)態(tài)解析在Java中的應(yīng)用 包含例子程序
下面小編就為大家?guī)?lái)一篇基于Protobuf動(dòng)態(tài)解析在Java中的應(yīng)用 包含例子程序。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-07-07mybatis中實(shí)現(xiàn)枚舉自動(dòng)轉(zhuǎn)換方法詳解
在使用mybatis的時(shí)候經(jīng)常會(huì)遇到枚舉類型的轉(zhuǎn)換,下面這篇文章主要給大家介紹了關(guān)于mybatis中實(shí)現(xiàn)枚舉自動(dòng)轉(zhuǎn)換的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起看看吧。2017-08-08在Java開(kāi)發(fā)中無(wú)法繞開(kāi)的SpringBoot框架詳解
SpringBoot是一個(gè)基于Spring框架的快速開(kāi)發(fā)框架,它的出現(xiàn)極大地簡(jiǎn)化了Spring應(yīng)用的開(kāi)發(fā)流程,SpringBoot是一個(gè)快速開(kāi)發(fā)的框架,它提供了一種快速構(gòu)建應(yīng)用程序的方式,本文給大家介紹在Java開(kāi)發(fā)中無(wú)法繞開(kāi)的框架:SpringBoot,感興趣的朋友一起看看吧2023-09-09MybatisPlus使用代碼生成器遇到的小問(wèn)題(推薦)
這篇文章主要介紹了MybatisPlus使用代碼生成器遇到的小問(wèn)題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-08-08Seata AT模式TransactionHook被刪除探究
這篇文章主要為大家介紹了Seata AT模式TransactionHook被刪除探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11web中拖拽排序和java后臺(tái)交互實(shí)現(xiàn)方法示例
這篇文章主要給大家介紹了關(guān)于web中拖拽排序和java后臺(tái)交互實(shí)現(xiàn)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-12-12