<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                前臺準備完畢后。接下來進行后臺的對接。后臺對接主要實現兩個接口:1. 根據ID獲取某位學生信息接口。 2. 更新某個學生信息的接口。接口規范在[本章節](http://www.hmoore.net/yunzhiclub/springboot_angular_guide/1378718)開始時已經給出。 前臺的主體編輯功能完成后,再來觀察相應的更新接口。此時發現接口的返回值并不符合前臺的數據要求: 前臺對編輯的返回值處理如下: ```javascript /** * 更新學生 * @param id id * @param student 學生 */ update(id: number, student: Student): Observable<Student?> { const url = `http://localhost:8080/Student/${id}`; return this.httpClient.put<Student>(url, student); } ``` * ? 更新接口應該返回學生 但前期定義的接口規范返回的卻是空內容: ![](https://img.kancloud.cn/74/cb/74cbeed4242a6cbff64327eadb20b009_910x162.png) 可以預見的是:如果按前面規定的接口規范開發,那么后臺的更新接口將無法滿足前臺的功能需求。若想滿足前臺的功能的需求,則需要變更后臺接口相應的返回值。由于此接口有了返回值,狀態碼也應該由204變更為200。 新的接口返回值如下: #### 響應(返回值)Responses | HTTP Code | Description | Schema | | --- | --- | --- | | **200** | 學生 | Student | 在相應功能的開發過程中,我們優先選擇開發前臺,然后再開發后臺的開發步驟也是基于這種接口規范可能會變更的現實。這種開發步驟能夠有效的避免一些接口定義無法滿足前臺實際需求的情況。先開發前臺再開發后臺,變更接口規范對后臺造成的影響最小。但如果先開發后臺再開發前臺就完全不一樣了。后臺開發完成后,前臺在使用的過程中發現適用有問題,此時就需要后臺進行修改來適應前臺。有時候這種修改會直接推翻后臺的邏輯性,使原后臺開發的接口的價值為0甚至為負值。 # 接口開發 先Thinking,再Coding: ![](https://img.kancloud.cn/21/bb/21bb73b3b14554c43ffdd39287e8a4fa_826x441.png) # GetById 按時序圖的反方向進行初始化: 數據倉庫層由于繼承了Crud接口,save方法已經由該接口提供了,所以直接忽略。 服務層初始化:service/StudentService.java ``` public interface StudentService { ...java /** * 查找學生 * @param id 學生ID * @return 學生 */ Student findById(@NotNull Long id); ``` 服務層初始化:service/StudentServiceImpl.java ```java public class StudentServiceImpl implements StudentService { ... @Override public Student findById(@NotNull Long id) { return null; } ``` C層初始化:controller/StudentController.java ```java public class StudentController { ... /** * 通過ID查詢學生 * @param id 學生ID * @return 學生 */ public Student getById(Long id) { return this.studentService.findById(id); } ``` > 回看KlassController中的獲取某個班級時,會發現其方法名命名為:get;但此處被命名為getById。當某個方法參數較少時,可以采用`xxxBy參數a參數b`的形式來進行命名,當參數較多時,則一般直接命名為xxx。 此時在常規的開發方法中,便可以啟用postman或是直接啟動前開來進行功能開發了。在教程中對于已經學習過的知識點,我們仍然優先使用單元測試的方法進行功能開發。 ## 功能開發 功能開發過程仍然按從后到前的開發步驟,在單元測試還沒有并熟練掌握前,這可以更好的支持傳統的測試方法。 ### M層 service/StudentServiceImpl.java ```java public class StudentServiceImpl implements StudentService { ... @Override public Student findById(@NotNull Long id) { Assert.notNull(id, "id不能為null"); ? return this.studentRepository.findById(id).get(); ? } ``` * ? 非null校驗,當傳入null時,直接拋出異常并附帶提示信息 * ? 調用倉庫層返回學生 ### M層單元測試 單元測試的過程中,如果單元測試的代碼過長或邏輯過于復雜,應該想辦法進行拆分,將測試粒度變小。本方法的測試邏輯相對簡單,是否將所有的功能放到一個單元測試中來進行測試,可以自主決定。教程中仍然采用粒度最小化原則進行測試。 測試粒度一:null測試 StudentServiceImplTest.java ```java /** * 參數為null測試 */ @Test(expected = IllegalArgumentException.class) public void findByIdNullArgument() { this.studentService.findById(null); } ``` 測試粒度2:調用測試 ```java /** * 調用測試 */ @Test public void findById() { // 準備調用時的參數及返回值 // 發起調用 // 斷言返回值與預期相同 // 斷言接收到的參數與預期相同 } ``` 按注釋補充代碼如下: ```java /** * 調用測試 */ @Test public void findById() { // 準備調用時的參數及返回值 Long id = new Random().nextLong(); Student mockReturnStudent = new Student(); Mockito.when(this.studentRepository.findById(id)).thenReturn(Optional.of(mockReturnStudent)); // 發起調用 Student student = this.studentService.findById(id); // 斷言返回值與預期相同 Assertions.assertThat(student).isEqualTo(mockReturnStudent); // 斷言接收到的參數與預期相同 ArgumentCaptor<Long> longArgumentCaptor = ArgumentCaptor.forClass(Long.class); Mockito.verify(this.studentRepository).findById(longArgumentCaptor.capture()); Assertions.assertThat(longArgumentCaptor.getValue()).isEqualTo(id); } ``` 單元測試通過,說明功能符合我們的預期。 ### C層 相對于M層,C層由于需要與前臺對接,所以在測試的過程中測試點相對要多一些。 初始化 StudentControllerTest ```java @Test public void getById() { // 準備傳入參數的數據 // 準備服務層替身被調用后的返回數據 // 按接口規范,向url以規定的參數發起get請求。 // 斷言請求返回了正常的狀態碼 // 斷言C層進行了數據轉發(替身接收的參數值符合預期) // 斷言返回的json數據符合前臺要求 } ``` 按注釋分步完成代碼: ``` import org.assertj.core.internal.bytebuddy.utility.RandomString; ? @Test public void getById() throws Exception { // 準備傳入參數的數據 Long id = new Random().nextLong(); // 準備服務層替身被調用后的返回數據 Student student = new Student(); student.setId(id); ? student.setSno(new RandomString(6).nextString()?); ? student.setName(new RandomString(8).nextString()); ? student.setKlass(new Klass()); ? student.getKlass().setId(new Random().nextLong()); ? Mockito.when(this.studentService.findById(Mockito.anyLong())).thenReturn(student); // 按接口規范,向url以規定的參數發起get請求。 // 斷言請求返回了正常的狀態碼 String url = "/Student/" + id.toString() ; MvcResult mvcResult = this.mockMvc.perform(MockMvcRequestBuilders.get(url)) .andExpect(MockMvcResultMatchers.status().isOk()) .andReturn(); // 斷言C層進行了數據轉發(替身接收的參數值符合預期) ArgumentCaptor<Long> longArgumentCaptor = ArgumentCaptor.forClass(Long.class); Mockito.verify(this.studentService).findById(longArgumentCaptor.capture()); Assertions.assertThat(longArgumentCaptor.getValue()).isEqualTo(id); // 斷言返回的json數據符合前臺要求 DocumentContext documentContext =JsonPath.parse(mvcResult.getResponse().getContentAsString()); ? LinkedHashMap studentHashMap = documentContext.json(); ? Assertions.assertThat(studentHashMap.get("id")).isEqualTo(Integer.valueOf(id.toString())); Assertions.assertThat(studentHashMap.get("sno")).isEqualTo(student.getSno()); Assertions.assertThat(studentHashMap.get("name")).isEqualTo(student.getName()); LinkedHashMap klassHashMap = (LinkedHashMap) studentHashMap.get("klass"); Assertions.assertThat(klassHashMap.get("id")).isEqualTo(Integer.valueOf(student.getKlass().getId().toString())); } ``` * ? 前臺獲取某個學生生,需要將這些值展示到V層或供C層使用,所以返回值在準備好這些數據。 * ? 另一種獲取隨機字符串的方法 * ?? 通過兩次轉換,將json字符串轉換為java中的LinkedHashMap對象 接下來,啟動單元測試并按單元測試提示完善功能代碼或修正單元測試代碼: ``` java.lang.AssertionError: Status Expected :200 Actual :404 <Click to see difference> ``` 404錯誤說明請求的地址未找到,該錯誤的產生無非就兩個原因:1. 請求時URL不小心拼寫錯了。 2. 后臺沒有對應建立好請求地址對應的映射。通過檢查發現當前屬于第2個原因。 ``` @GetMapping("{id}") ? public Student getById(Long id) { return this.studentService.findById(id); } ``` 再次進行測試: ``` Assertions.assertThat(longArgumentCaptor.getValue()).isEqualTo(id); org.junit.ComparisonFailure: Expected :555414603L ? Actual :null ``` * ? 該值是隨機生成的,每次執行單元測試都會生成一個隨機值,因面你本地顯示的值與教程不同是正確的。 該錯誤提示我們:應該是使用傳入的ID值來調用M層,但實際上卻使用了null來調用。這是由于我們在C層未能成功的接收傳入ID值造成的。 ```java public Student getById(@PathParam("id")? Long id) { return this.studentService.findById(id); } ``` 再次測試仍然是剛剛的錯誤,這仍然說明C層沒有接收到傳入的ID值。最終通過檢查發現原來獲取路徑變量的應該使用`@PathVariable`而非`@PathParam`: ``` public Student getById(@PathVariable? Long id) { return this.studentService.findById(id); } ``` 再次進行單元測試,測試通過。單元測試看似寫了較多的代碼,但其實開發的效率并不低。在這種開發模式下,我們無需向數據庫中寫入真實的數據(實際上這項工作在一些稍大型的一些有外鍵約束的項目中非常的沉重),也不會額外啟動一個前臺或是類似于postman的工作。更重要的是還為此代碼在后期項目更新的過程中提供了功能保障。長期來看,其不失為一種高效的開發方式。 ## JsonPath 在將json字符串變更為java可識別的對象時,使用了`JsonPath.parse`方法。實際上springboot已經內置了`JsonPah`并將其快速的應用到了模擬請求返回值的斷言中。剛剛單元測試中對json數據的斷言還可以改寫成這樣。 ``` @Test public void getById() throws Exception { // 準備傳入參數的數據 Long id = new Random().nextLong(); // 準備服務層替身被調用后的返回數據 Student student = new Student(); student.setId(id); student.setSno(new RandomString(6).nextString()); student.setName(new RandomString(8).nextString()); student.setKlass(new Klass()); student.getKlass().setId(new Random().nextLong()); Mockito.when(this.studentService.findById(Mockito.anyLong())).thenReturn(student); // 按接口規范,向url以規定的參數發起get請求。 // 斷言請求返回了正常的狀態碼 String url = "/Student/" + id.toString() ; MvcResult mvcResult = this.mockMvc.perform(MockMvcRequestBuilders.get(url)) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.jsonPath("id").value(id)) ? .andExpect(MockMvcResultMatchers.jsonPath("sno").value(student.getSno())) ? .andExpect(MockMvcResultMatchers.jsonPath("name").value(student.getName())) ? .andExpect(MockMvcResultMatchers.jsonPath("klass.id").value(student.getKlass().getId())) ? .andExpect(MockMvcResultMatchers.jsonPath("klass.name").value(student.getKlass().getName())) ? .andReturn(); // 斷言C層進行了數據轉發(替身接收的參數值符合預期) ArgumentCaptor<Long> longArgumentCaptor = ArgumentCaptor.forClass(Long.class); Mockito.verify(this.studentService).findById(longArgumentCaptor.capture()); Assertions.assertThat(longArgumentCaptor.getValue()).isEqualTo(id);f } ``` # 參考文檔 | 名稱 | 鏈接 | 預計學習時長(分) | | --- | --- | --- | | 源碼地址 | [https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step4.7.4](https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step4.7.4) | - | | JsonPath | [https://github.com/json-path/JsonPath](https://github.com/json-path/JsonPath)| - |
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看