SpringBoot單元測試使用@Test沒有run方法的解決方案
SpringBoot單元測試使用@Test沒有run方法
吐瞭!一個關鍵字,糾錯兩小時,看瞭十幾篇博客。。。。最後重新建測試類發現@Test又有用,結果發現是因為默認的Tests測試類沒有public關鍵字!
這個破錯改瞭兩小時。。。
==後續來瞭:==
原因找到瞭
建項目的時候是默認的2.3.0,所以默認創建的類結構應該是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。之前因為改成瞭2.1.7.RELEASE,版本不同,項目的結構也不同。
現在用回2.3.0RELEASE
是可以正常跑的。。。而且也沒有瞭@RunWith註解
SpringBoot寫單元測試遇到的坑
近期,項目需要寫單元測試。我著手的項目是用SpringBoot寫的。所以就簡單的研究瞭一下如何使用。在使用中遇到不少問題,不得已換瞭一種方式寫測試用例,寫完之後總感覺不太爽。今天在Spring官網上學一個新的用法,發現這種測試方法使用後沒有問題。所以來寫一點筆記。
SpringBoot怎麼寫單元測試
SpringBoot提供註解的方式編寫單元測試,可以使用SpringBootTest註解來標示測試類。
@RunWith(SpringRunner.class) @SpringBootTest public class SpringBootTest{ @Test public void method(){ } }
這樣寫隻能解決沒有一些配置文件的測試邏輯,比如沒有數據庫配置、數據庫連接池配置等。如果有這些配置,你就需要這樣寫瞭。
@RunWith(SpringRunner.class) @SpringBootTest(classes = Application.class) @Test public void method(){ }
這樣就可以正常運行瞭。
測試controller類。使用瞭Mock,網上大多流傳的是下面這種方法,添加@WebAppConfiguration,使用MockMvc去進行單元測試,但是我的項目如下使用就出現瞭問題,執行的時候找不到Controller類,網上百度瞭各種方法都不管用。都會報 no bean of 'controller' type found錯誤。
@RunWith(SpringRunner.class) @SpringBootTest(classes = Application.class) @WebAppConfiguration public class ControllerTest { private MockMvc mockMvc; @Autowired private WebApplicationContext wac; @Before // 在測試開始前初始化工作 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); }
後來換瞭一種方式直接new個controller。測試運行後不報no bean of 'controller' type found錯誤瞭,但是在controller中使用的service報瞭空指針異常NPE,傳遞性就很明顯瞭,controller是new的一個對象,所以註解不起作用,service就為null。
最後通過使用@AutoConfigureMockMvc+@MockBean的方式可以實現簡單的單元測試,並且不會對數據產生影響,且不會對數據庫產生影響。
@RunWith(SpringRunner.class) @SpringBootTest(classes = Application.class) @AutoConfigureMockMvc public class ImkfMessageReportControllerTest { /** * 初始化MockMvc */ @Autowired private MockMvc mvc; /** * 測試的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進行單元測試
上面是使用MockMvc,雖然能夠驗證短鏈接甚至service代碼邏輯的正確性,能夠正常測試接口的問題。但是缺點也不少,比如,覆蓋率並沒有提升。Mockito是一個非常好用的單元測試工具,它的實現原理是繼承要Mock的類,將所有的公有方法進行重寫
@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)來進行mocktio測試,註解@Mock標記一個類或者接口是需要被mock的,在Mock的過程中就相當於@Resource,但是註意一點是Mock是繼承or實現瞭Mock的類,所以Mock出來的方法,全是null或者返回值為null。@InjectMocks將所有的mock對象都放入需要測試的類的對象中。在上面的saveTest方法裡面調用到UserMapper.insert(),那麼需要對UserMapper.insert()進行打樁,設置預期返回值。
打樁的時候需要註意:傳遞的參數(如果有)必須為調用時的同一個對象或者相同值,如果傳入的參數是一個對象,那麼需要對這個對象進行打樁,再打樁這個方法。比如,when(userMapper.insert(user)).thenReturn(rUser),插入一個user對象,如果user插入之前要進行校驗或者其他操作,需要對這個對象進行打樁(當然pojp對象可以直接new)。
如果插入的對象非常復雜,用構造方法來構造一個空對象,或者構造方法所用的對象不能直接構造,但是沒有public的方法來設置值,該如何解決這個問題?我們知道一個對象的類可能有get方法(不一定是get,但是隻要我們想獲取這個對象中的參數,那麼就有public的方法獲取),我們可以通過Mock這個對象,在將要測試的方法體內,如果某行調用瞭這個對象的任意方法(toString()、equals()、get()),我們都可以以相同的參數(如果遇到參數未知可以用any(),一般都能知道)進行打樁後設置返回值,這樣就能通過參數校驗等環節,執行後面的代碼邏輯,同時能夠提高覆蓋率,偽代碼如下。
@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");
通過真實測試用例測試代碼
Mockito測試需要設置參數和預期返回值,在方法體中遇到的所有未知對象(除瞭方法體中new的對象不需要)都需要進行模擬,但是在SpringBoot代碼剛剛完成的初期時,跟想模擬真實場景下進行單元測試代碼問題or配置問題,那麼通過自動註入的方式引入對象是一種更好的選擇。
ProviderServiceImpl.java -----服務類 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 -----測試類 @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 需要跟啟動類main在同一級目錄下,如果跟mvc在同一層可以會出現部分bean掃描不到的情況。如目錄層級很深或者程序啟動比較慢的話,可以去掉SpringBootTest(去掉後就不啟動程序,隻會運行該測試),運行一下,測試結果如下:
通過這種註解的方式,可以測試dubbo連接(Refernce註解),可以測試controller層,redis數據,mysql數據,都會真實模擬,你隻需要在註入你需要測試的類,在類的入口傳入測試參數,在測試過程中,最好采用debug的方式,這樣你可以看到每一步的數據,也便於定位程序的問題(當然也可以出現問題時使用debug)。
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。
推薦閱讀:
- 單元測試 @mock與@SpringBootTest的使用
- Spring 單元測試中如何進行 mock的實現
- Java單元測試Mockito的使用詳解
- Junit Mockito實現單元測試方法介紹
- Java Spring Boot實戰練習之單元測試篇