<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>

                本節來共同完成刪除時彈窗功能。 首先按啟動數據庫、前后臺、添加模擬數據的步驟進行一些數據準備: ![](https://img.kancloud.cn/1f/00/1f008fab3ed97c11223743f9b08a3881_1083x211.png) # confirm confirm是任何瀏覽器均支持的方法,用于做一些提示的功能。通過簡單的代碼來了解一下: src/app/student/index/index.component.ts ```javascript /** * 刪除學生 * @param student 學生 */ onDelete(student: Student): void { const result = confirm('這里是提示的消息'); // ? if (result) { alert('用戶點擊了確認'); // ? } else { alert('用戶點擊了取消'); } alert('用戶做出選擇后,代碼繼續執行'); } ``` * ? 彈出確認框,提示內容:這里是提示的消息 * ? alert是瀏覽器彈窗的功能。與confirm一樣都是比較古老的方法。 測試: ![](https://img.kancloud.cn/7e/88/7e883bd7922ea33a8834ef967aeb4e5b_810x377.gif) 觀察測試結果總結出以下特點: [] confirm執行時會中斷代碼的執行 [] 點擊確認后將返回true [] 點擊取消后將返回false # 功能開發 參考時序圖,在C層中給出用戶提示框后,若用戶點擊了"確認"則調用M層的`deleteById`方法,若點擊"取消"則取消刪除。 ![](https://img.kancloud.cn/a4/16/a416a43526a68b4e7477617cde106449_721x491.png) 單元測試可以使用最少的成本來搭建起開發的環境,對于已經學習過的開發方法繼續采用單元測試的方式進行功能相關的開發。按TDD的開發理論,先嘗試寫點測試的代碼如下: src/app/student/index/index.component.spec.ts ```javascript fit('onDelete', () => { // 替身及模似數據的準備 // 調用方法 // 斷言 }); ``` 無論什么樣的單元測試,基本上都是這個邏輯,先依據要測試方法的內部調用情況來準備替身和模擬數據,在此基本上再發請調用,最后進行斷言以證明方法的執行符合預期。 我們一直說先Thinking,在Coding這也在單元測試中被充分的體現出來。因為如果沒有充分的思索功能的實現步驟是無法動手寫單元測試的。 在時序圖中有一個條件判斷,即:用戶選擇確認與選擇取消是兩條不同的線,本著細化測試粒度的原則,重新歸劃測試用例如下: src/app/student/index/index.component.spec.ts ```javascript fit('onDelete -> 取消刪除', () => { // 替身及模似數據的準備 // 調用方法 // 斷言 }); fit('onDelete -> 確認刪除', () => { // 替身及模似數據的準備 // 調用方法 // 斷言 }); ``` ## 取消刪除 用戶取消刪除的操作較簡單,當用戶點擊取消時,斷言未進行M層刪除方法的調用,同時也沒有 src/app/student/index/index.component.spec.ts ``` fit('onDelete -> 取消刪除', () => { // 替身及模似數據的準備 const studentService: StudentService = TestBed.get(StudentService); spyOn(studentService, 'deleteById'); spyOn(window, 'confirm').and.returnValue(false); // ? // 調用方法 component.onDelete(null); // 斷言 expect(studentService.deleteById).toHaveBeenCalledTimes(0); }); ``` * ? javascript是完全面向對象的語言。confirm方法只所以可以直接調用,根本的原因是由于其存在于對象`window`上 功能代碼 測試結果: ![](https://img.kancloud.cn/35/20/35206f4ca0c929cf31a75cc9162bee60_432x112.png) 這是由于在此組件的測試過程中指定了使用`StudentStubService`來替代`StudentService`,而`StudentStubService`上并不存在`deleteById`方法。加入相應方法: src/app/service/student-stub.service.ts ``` deleteById(id: number) { } ``` 再次運行單元測試,通過。 ## 確認刪除 確認刪除的功能比取消刪除要復雜一些,它不僅要向M層發請請求。還要在接收到M層操作成功的消息后在C層的數據中移除相應的`student`。 先斷言要M層發請請求: src/app/student/index/index.component.spec.ts ``` fit('onDelete -> 確認刪除', () => { // 替身及模似數據的準備 const studentService = TestBed.get(StudentService); spyOn(studentService, 'deleteById'); spyOn(window, 'confirm').and.returnValue(true); // 調用方法,刪除第一個學生 const student = component.pageStudent.content[0]; component.onDelete(student); // 斷言 expect(studentService.deleteById).toHaveBeenCalledWith(student.id); }); ``` ![](https://img.kancloud.cn/f4/0b/f40b7d5d1d45edaf3d5f1c0503a77473_641x76.png) 測試的結果符合預期,因為C層的代碼還停留在一些提示功能上,補充功能如下: src/app/student/index/index.component.ts ```javascript /** * 刪除學生 * @param student 學生 */ onDelete(student: Student): void { const result = confirm('這里是提示的消息'); if (result) { this.studentService.deleteById(student.id); } else { alert('用戶點擊了取消'); } alert('用戶做出選擇后,代碼繼續執行 '); } ``` ![](https://img.kancloud.cn/fd/96/fd96a74a7b3e271d73ea4e0ac42933bf_360x168.png) 接著繼續完成將刪除的學生由組件C層數據中移除的操作。 src/app/student/index/index.component.spec.ts ```javascript fit('onDelete -> 確認刪除', () => { ... // 斷言 expect(studentService.deleteById).toHaveBeenCalledWith(student.id); // 斷言刪除的學生成功的由前臺移除 let found = false; component.pageStudent.content.forEach(value => { // ? if (value === student) { found = true; } }); expect(found).toBeFalsy(); }); ``` * ? 遍歷學生,斷言找不到被刪除掉的學生了 補充功能代碼: src/app/student/index/index.component.ts ```javascript onDelete(student: Student): void { const result = confirm('這里是提示的消息'); if (result) { this.studentService.deleteById(student.id) .subscribe(() => { // ? this.pageStudent.content.forEach((value, key) => { if (value === student) { this.pageStudent.content.splice(key, 1); } }); }); } else { alert('用戶點擊了取消'); } alert('用戶做出選擇后,代碼繼續執行 '); } ``` * ? 實現**某個具有不確認定的操作完成以后**再執行其它操作的方法有兩個:1 是使用承諾(promise);2是使用觀察者(Observable)。在angular中廣泛地使用了觀察者替代了angularjs中的promise。 這里使用了`subscribe`,則要求`studentService.deleteById`方法的返回值為`Observable`。 src/app/service/student.service.ts ```javascript deleteById(id: number): Observable<void> ?{ return null; } ``` * ? 執行成功后返回void(空值) ![](https://img.kancloud.cn/08/81/0881ad9dad4c811bdb856e69a9e49c67_962x145.png) 單元測試報錯說:在測試文件的485行(你練習的代碼可以不是485行,按提示對應找到相關行即可)發生了,在`undefined`類型上調用`subscribe`方法的錯誤,對應代碼如下: src/app/student/index/index.component.spec.ts ```javascript component.onDelete(student); ``` 此代碼調用了`component.onDelete`方法,并沒有調用`subscribe`方法的相關代碼。所以必然不是本行代碼出錯,而是`component.onDelete`的方法在執行時發生了錯誤。`component.onDelete`中恰好存在以下代碼: src/app/student/index/index.component.ts ```javascript this.studentService.deleteById(student.id) .subscribe(() => { ``` 也就是說此時`this.studentService.deleteById(student.id)`的返回值為`undefined`,所以才發生單元測試中報出的`TypeError: Cannot read property 'subscribe' of undefined`的錯誤。 這是由于在單元測試中,使用`spyOn(studentService, 'deleteById');`在設置`deleteById`的替身時沒有為該替身設置返回值,此時默認的返回值便是undefined,近而引發了上述錯誤。為其設置返回值以解決問題: src/app/student/index/index.component.spec.ts ``` import {BehaviorSubject} from 'rxjs'; ... const studentService = TestBed.get(StudentService); const subject = new BehaviorSubject<void>(undefined); // ? spyOn(studentService, 'deleteById').and.returnValue(subject); // ? spyOn(window, 'confirm').and.returnValue(true); ``` * ? 區別于Subject,BehaviorSubject在初始化時可以裝入一個值。由于此時的可觀察者所攜帶的值的類型為void,所以此處傳入undefined或null做為初始值 ![](https://img.kancloud.cn/03/2a/032a19ceed6951d319f76521d0c2f9ff_298x191.png) 最后,去除或修正一些C層中測試的痕跡。 src/app/student/index/index.component.ts ```javascript onDelete(student: Student): void { const result = confirm('這里是提示的消息'); if (result) { this.studentService.deleteById(student.id) .subscribe(() => { this.pageStudent.content.forEach((value, key) => { if (value === student) { this.pageStudent.content.splice(key, 1); } }); }); } else { alert('用戶點擊了取消'); // ? console.log('用戶點擊了取消'); // ? } alert('用戶做出選擇后,代碼繼續執行 '); // ? } ``` # M層 M層deleteById方法的開發主要參考相應的接口規范。主要功能點如下: * 向地址/student/id發起請求 * 請求方式為get * 返回值為可被觀察者,該觀察者攜帶的內容為`void` src/app/service/student.service.spec.ts ```javascript fit('deleteById', () => { // 模擬數據及替身的準備 // 調用方法 // 斷言發起了http請求 // 請求的方法為delete // 返回值為可被觀察者,該觀察者攜帶的內容為`void` }); ``` 嘗試完成代碼: ```javascript fit('deleteById', () => { // 模擬數據及替身的準備 // 調用方法 const id = Math.floor(Math.random() * 100); let called = false; service.deleteById(id).subscribe(() => { called = true; }); // 斷言發起了http請求 const httpTestingController: HttpTestingController = TestBed.get(HttpTestingController); const req = httpTestingController.expectOne(`http://localhost:8080/student/${id}`); // 請求的方法為delete expect(req.request.method).toEqual('DELETE'); // 返回值為可被觀察者,該觀察者攜帶的內容為`void` expect(called).toBeFalsy(); req.flush(of()); expect(called).toBeTruthy(); }); ``` ## 完成功能代碼 如果測試代碼都難不倒我們的話,功能性的代碼就更不會有問題了。 src/app/service/student.service.ts ```javascript /** * 刪除學生 * @param id 學生id */ deleteById(id: number): Observable<void> { const url = `http://localhost:8080/Student/${id}`; return this.httpClient.delete<void>(url); } ``` ![](https://img.kancloud.cn/a9/07/a90796811ba2b1f4e680e8fa483f7b4e_869x94.png) 測試結果說:根本就沒有向`http://localhost:8080/student/20"`這個地址發起請求......此時說明:要么測試代碼錯了,要么功能代碼錯了。經過排查確認,原來在測試代碼中的請求地址被誤輸入為小寫的`student`了,而正確的應該是大寫的`Student`。有時候就這么一個小小的大小寫問題也會引發大問題。單元測試與功能開發分別寫一次,兩次都寫錯誤的概率要比一次寫錯的概率小多了。 修正如下: src/app/service/student.service.spec.ts ```javascript fit('deleteById', () => { ... const req = httpTestingController.expectOne(`http://localhost:8080/student/${id}`); // ? const req = httpTestingController.expectOne(`http://localhost:8080/Student/${id}`); // ? ... }); ``` 再次測試,通過。 # 參考文檔 | 名稱 | 鏈接 | 預計學習時長(分) | | --- | --- | --- | | 源碼地址 | [https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step4.8.2](https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step4.8.2) | - | | confirm | [https://www.runoob.com/jsref/met-win-confirm.html](https://www.runoob.com/jsref/met-win-confirm.html) | 5 |
                  <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>

                              哎呀哎呀视频在线观看