Spring Boot Mock單元測試學習總結


單元測試的方法有很多種,比如使用Postman、SoapUI等工具測試,當然,這里的測試,主要使用的是基於RESTful風格的SpringMVC的測試,我們可以測試完整的Spring MVC流程,即從URL請求到控制器處理,再到視圖渲染都可以測試。下面我主要總結下Spring Boot基於Mock的方式對控制層Controller和服務層Serivce的單元測試。盡管這種的文章已經有很多,我的總結只是作為自己學習的一個承載,總結有誤的地方歡迎小伙伴們指正,同時也希望能幫助跟我一樣還在學習的小伙伴們。

在介紹Mock API測試方法之前先介紹下Spring MVC測試框架提供的兩種方式:

獨立安裝測試集成Web環境測試(此種方式並不會集成真正的web環境,而是通過相應的Mock API進行模擬測試,無須啟動服務器)。

MockMvcBuilder介紹:

是用來構造MockMvc的構造器,其主要有兩個實現:StandaloneMockMvcBuilder和DefaultMockMvcBuilder,StandaloneMockMvcBuilder繼承了DefaultMockMvcBuilder。直接使用靜態工廠MockMvcBuilders創建即可:

1. 集成Web環境測試:

    MockMvcBuilders.webAppContextSetup(WebApplicationContext context):指定WebApplicationContext,將會從該上下文獲取相應的控制器並得到相應的MockMvc;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes=MockServletContext.class)
@WebAppConfiguration
public class StudentControllerTest {
    
    @Autowired
    private WebApplicationContext wac;
    
    private MockMvc mockMvc;

    @Before
    public void setup() {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); // 構造MockMvc
    }
    // ...
}

注意:

(1)@WebAppConfiguration:測試環境使用,用來表示測試環境使用的ApplicationContext將是WebApplicationContext類型的;value指定web應用的根;
(2)通過@Autowired WebApplicationContext wac:注入web環境的ApplicationContext容器;
(3)然后通過MockMvcBuilders.webAppContextSetup(wac).build()創建一個MockMvc進行測試;

2.獨立測試方式

  MockMvcBuilders.standaloneSetup(Object... controllers):通過參數指定一組控制器,這樣就不需要從上下文獲取了;

 (SpringMVC測試需要添加:mockito-core和mockito-all依賴)

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath*:/spring-context.xml"})
public class DemoTest {

    @Mock
    private StudentService studentService;
    
    @InjectMocks
    private StudentController studentController;
    
    private MockMvc mockMvc;
    
    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
        this.mockMvc = MockMvcBuilders.standaloneSetup(bookController).build();
    }
    
    @Test
    public void testXX() throws Exception {
        //...
    }
}

主要是兩個步驟:
(1)首先自己創建相應的控制器,注入相應的依賴
(2)通過MockMvcBuilders.standaloneSetup模擬一個Mvc測試環境,通過build得到一個MockMvc。

perform:執行一個RequestBuilder請求,會自動執行SpringMVC的流程並映射到相應的控制器執行處理;
andExpect:添加ResultMatcher驗證規則,驗證控制器執行完成后結果是否正確;
andDo:添加ResultHandler結果處理器,比如調試時打印結果到控制台;
andReturn:最后返回相應的MvcResult;然后進行自定義驗證/進行下一步的異步處理;

MockMvcRequestBuilders主要API:

MockHttpServletRequestBuilder get(String urlTemplate, Object... urlVariables):根據uri模板和uri變量值得到一個GET請求方式的MockHttpServletRequestBuilder;如get("/user/{id}", 1L);

MockHttpServletRequestBuilder post(String urlTemplate, Object... urlVariables):同get類似,但是是POST方法;

MockHttpServletRequestBuilder put(String urlTemplate, Object... urlVariables):同get類似,但是是PUT方法;

MockHttpServletRequestBuilder delete(String urlTemplate, Object... urlVariables) :同get類似,但是是DELETE方法;

MockHttpServletRequestBuilder options(String urlTemplate, Object... urlVariables):同get類似,但是是OPTIONS方法;

Mock測試過程:

1、mockMvc.perform執行一個請求(MockMvcRequestBuilders.get("/user/1")構造一個請求).
2、設置參數(這一步其實可以設置很多參數,MockMvc提供了豐富的方法)
3、mockMvc調用perform,調用controller的業務處理邏輯
4、perform返回ResultActions,返回操作結果,通過ResultActions,提供了統一的驗證方式。(

        ResultActions.andExpect添加執行完成后的斷言,

       ResultActions.andDo添加一個結果處理器,表示要對結果做點什么事情,比如此處使用MockMvcResultHandlers.print()輸出整個響應結果信息。

       ResultActions.andReturn表示執行完成后返回相應的結果。

測試說明:

1. 依賴包導入:pom.xml中僅依賴spring-boot-starter-test,它把會把測試相關的依賴全部引入。

2. 在測試類上的注解,常用的注解有三個:

 @RunWith(SpringJUnit4ClassRunner.class)//引用Spring-Test測試框架支持
 @SpringApplicationConfiguration(classes = StartApp.class) // StartApp :可以是Spring Boot的啟動類,也可以使用MockServletContext來構建一個空的WebApplicationContext,這樣我們創建的StudentController就可以在@Before函數中創建並傳遞到MockMvcBuilders.standaloneSetup()函數中。
 @WebAppConfiguration //用來表示測試環境使用的ApplicationContext將是WebApplicationContext類型的

3. 測試類的文件結構,保持src/test/java和src/main/java結構一致,即:包+文件夾。測試配置文件任然用src/main/resources的。

根據如上介紹看下如下案例:

定義Student實體類:

public class Student implements Serializable{

    /**
     * 
     */
    private static final long serialVersionUID = -9143765513634702342L;

    private Long id;
    
    private String name;
    
    private String age;
    
    private String address;

    public String getName() {
        return name;
    }
//省略get/set方法...
}
View Code

定義IStudentService接口類:

public interface IStudentService {
 
    public List<Student> getStudentList();
    
    public int addStudent(Student student);
    
    public Student getStudent(Long id);
    
    public int updateStudent(Student student);
    
    public int deleteStudent(Long id);
}
View Code

定義IStudentService接口類實現:

@Service
public class StudentService implements IStudentService {

    /**
     * 定義一個線程安全的集合(集合驗證時獲取不到存的值,不影響學習mock測試方法)
     */
    static Map<Long, Student> students = Collections.synchronizedMap(new HashMap<Long, Student>());
    
    @Override
    public List<Student> getStudentList() {
        List<Student> stuList = new ArrayList<Student>();
        stuList.addAll(students.values());
        return stuList;
    }

    @Override
    public int addStudent(Student student) {
        students.put(student.getId(), student);
        return 0;
    }

    @Override
    public Student getStudent(Long id) {
        Student stu = students.get(id);
        return stu;
    }

    @Override
    public int updateStudent(Student student) {
        Long id = student.getId();
        Student stu = students.get(id);
        stu.setAddress(student.getAddress());
        stu.setAge(student.getAge());
        stu.setName(student.getName());
        students.put(id, stu);
        return 0;
    }

    @Override
    public int deleteStudent(Long id) {
        students.remove(id);
        return 0;
    }
}
View Code

定義Controller類:

@RestController
@RequestMapping(value="/stu")
public class StudentController {

    @Autowired
    private IStudentService iStudentService;
    
    @RequestMapping(value="/",method=RequestMethod.GET)
    @ResponseBody
    public List<Student> getStudentList(){
        List<Student> list = iStudentService.getStudentList();
        System.out.println("==="+list.size());
        return list;
    }
    
    @RequestMapping(value="/{id}",method=RequestMethod.GET)
    @ResponseBody
    public Student getStudent(@PathVariable(value="id")Long id){
        return iStudentService.getStudent(id);
    }
    
    @RequestMapping(value = "/", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    @ResponseBody
    public String addStudent(@ModelAttribute Student student) {
        iStudentService.addStudent(student);
        return "success";
    }
    
    @RequestMapping(value = "/", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    @ResponseBody
    public String updateStudent(@ModelAttribute Student student) {
        iStudentService.addStudent(student);
        return "success";
    }
    
    @RequestMapping(value="/{id}",method=RequestMethod.DELETE)
    public String deleteStudent(@PathVariable Long id){
        iStudentService.deleteStudent(id);
        return "success";
    }
}
View Code

定義Controller測試類:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes=MockServletContext.class)
@WebAppConfiguration
public class StudentControllerTest {

    private static final Log log = LogFactory.getLog(StudentApplicationTest.class);
    
//      @Autowired
//      private WebApplicationContext context;
    
    @Mock
    private IStudentService iStudentService;

    //@Mock: 創建一個Mock.
    //@InjectMocks: 創建一個實例,其余用@Mock(或@Spy)注解創建的mock將被注入到用該實例中。
    @InjectMocks
    private StudentController studentController;
    
    private MockMvc mvc;
    
    private final String json = "[{\"id\":1,\"name\":\"xiao\",\"age\":27,\"address\":\"湖北\"}]";
    
    @Before
    public void setUp(){
        MockitoAnnotations.initMocks(this);
        mvc = MockMvcBuilders.standaloneSetup(studentController).build();////設置要mock的Controller類
        //mvc = MockMvcBuilders.webAppContextSetup(context).build();//建議使用這種
    }
    
    @Test
    public void testStudentController() throws Exception {
        log.info("開始測試=================");
        log.info("測試新增學生===================");
        RequestBuilder request = post("/stu/").param("id", "1")
                                 .param("name", "xiao")
                                 .param("age", "27")
                                 .param("address","湖北");
        mvc.perform(request).andExpect(content().string(equalTo("success"))).andDo(print());
        
        log.info("測試獲取學生============");
        request = get("/stu/");
        mvc.perform(request)
           .andExpect(status().isOk())
           .andExpect(content().string(equalTo(json)));
    }
}
View Code

定義Service測試類:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes=Application.class)
@WebAppConfiguration
public class StudentServiceTest {

    @Autowired
    private IStudentService iStudentSerivce;
    
    @Test
    public void testAddStudent(){
        Student stu = new Student();
        stu.setId(1l);
        stu.setAge("27");
        stu.setName("xiaoming");
        stu.setAddress("China");
        int result = iStudentSerivce.addStudent(stu);
        Assert.assertEquals(0, result);
    }
    
    @Test
    public void testgetStudent(){
        Student stu = iStudentSerivce.getStudent(1l);
        Assert.assertEquals(stu.getName(), "xiaohua");
    }
}
View Code

附單元測試相關知識:

Junit單元測試:

//所有測試方法執行前.執行一次,作用:整體初始化
@BeforeClass

//所有測試方法完成后,執行一次,作用:銷毀和釋放資源
@AfterClass

//每個測試方法前執行,作用:初始化方法
@Before

//每個測試方法后執行,作用:還原現場
@After

// 測試方法超過1000毫秒,記為超時,測試失敗
@Test(timeout = 1000)

// 測試方法期望得到的異常類,如果方法執行沒有拋出指定的異常,則測試失敗
@Test(expected = Exception.class)

// 執行測試時將忽略掉此方法,如果用於修飾類,則忽略整個類
@Ignore(“not ready yet”)
@Test

@RunWith
在JUnit中有很多個Runner,他們負責調用你的測試代碼,每一個Runner都有各自的特殊功能,你要根據需要選擇不同的Runner來運行你的測試代碼。

如果我們只是簡單的做普通Java測試,不涉及spring Web項目,你可以省略@RunWith注解,這樣系統會自動使用默認Runner來運行你的代碼。

Assert斷言方法介紹:

   1、assertEquals

  函數原型1:assertEquals([String message],expected,actual)

參數說明:

message是個可選的消息,假如提供,將會在發生錯誤時報告這個消息。

  expected是期望值,通常都是用戶指定的內容。

actual是被測試的代碼返回的實際值。

例:assertEquals("equals","1","1");

  函數原型2:assertEquals([String message],expected,actual,tolerance)

參數說明:

message是個可選的消息,假如提供,將會在發生錯誤時報告這個消息。

  expected是期望值,通常都是用戶指定的內容。

  actual是被測試的代碼返回的實際值。

  tolerance是誤差參數,參加比較的兩個浮點數在這個誤差之內則會被認為是

  相等的。

  例:assertEquals ("yes",5.8,11.0/2.0,0.5);

  2、assertTrue

   函數原型:assertTrue ([String message],Boolean condition)

   參數說明:

message是個可選的消息,假如提供,將會在發生錯誤時報告這個消息。

       condition是待驗證的布爾型值。

   該斷言用來驗證給定的布爾型值是否為真,假如結果為假,則驗證失敗。當然,更有驗證為假的測試條件:

          函數原型:assertFalse([String message],Boolean condition)

          該斷言用來驗證給定的布爾型值是否為假,假如結果為真,則驗證失敗。

       例: assertTrue("true",1==1);

              assertFalse("false",2==1);

  3、assertNull

  函數原型:assertNull([String message],Object object)

參數說明:

message是個可選的消息,假如提供,將會在發生錯誤時報告這個消息。

  object是待驗證的對象。

  該斷言用來驗證給定的對象是否為null,假如不為null,則驗證失敗。相應地,還存在能夠驗證非null的斷言:

  函數原型:assertNotNull([String message],Object object)

該斷言用來驗證給定的對象是否為非null,假如為null,則驗證失敗。

例:assertNull("null",null);

       assertNotNull("not null",new String());

  4、assertSame

  函數原型:assertSame ([String message], expected,actual)

參數說明:

message是個可選的消息,假如提供,將會在發生錯誤時報告這個消息。

  expected是期望值。

  actual是被測試的代碼返回的實際值。

  該斷言用來驗證expected參數和actual參數所引用的是否是同一個對象,假如不是,則驗證失敗。相應地,也存在驗證不是同一個對象的斷言:

  函數原型:assertNotSame ([String message], expected,actual)

該斷言用來驗證expected參數和actual參數所引用的是否是不同對象,假如所引用的對象相同,則驗證失敗。

例:assertSame("same",2,4-2);

        assertNotSame("not same",2,4-3);

  5、Fail

  函數原型:Fail([String message])

參數說明:

message是個可選的消息,假如提供,將會在發生錯誤時報告這個消息。

  該斷言會使測試立即失敗,通常用在測試不能達到的分支上(如異常)。

:新版的Junit中,assertEquals 方法已經被廢棄,它建議我們使用assertArrayEquals,旨在讓我們測試一個方法的時候多傳幾種參數進行多種可能性測試。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM