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

                [Test-driven development 測試驅開發](https://zh.wikipedia.org/wiki/%E6%B5%8B%E8%AF%95%E9%A9%B1%E5%8A%A8%E5%BC%80%E5%8F%91)是一種軟件開發過程中的應用方法,以其倡導先寫測試程序,然后編碼實現其功能得名。在實際的使用過程中,筆者認為其優缺點如下: * [ ] 優點1:先寫測試代碼這強制的要求我們在完成功能實現前明確自己當前想要功能的輸入與輸出。 * [ ] 優點2:強制我們在正式動手開發前,在心中梳理代碼的編寫流程。 * [ ] 優點3:會提升我們對細節的把控能力。 * [ ] 缺點1:對編寫者的編寫技能要求較高。對于初學者而言,還停留在排查一些語法錯誤的階段。而此時如果單元測試報錯,初學者很難一下判斷出是測試單元出了問題還是功能代碼出了問題。 * [ ] 缺點2:對于初學者而言,會放慢其開發的速度。 * [ ] 缺點3:在編寫測試時全憑想像,對開發經驗、項目的全局掌握能力、抽象能力等均要求較高。 由于上述優缺點的存在,所以我們建議在實際應用TDD進行開發時應遵循以下幾點開發原則: * [ ] 如果該功能有UI支持,在編寫測試代碼前應該先開發原型。 * [ ] 在編寫測試代碼前應該保障被測試主體已初始化。 * [ ] 在編寫測試代碼的過程中,應該遵循敏捷開發原則,即測試代碼與功能交替進行迭代開發。 * [ ] 功能較復雜時,應該對復雜的功能進行拆解,分別對子功能進行測試開發。 按上述的原則,我們首先進行原型的開發,然后進行功能代碼初始化,接著開發測試代碼,最后在測試代碼的協助下完成功能開發。 ## 原型開發 打開klass/index/index.component.html ``` <td> <a routerLink="./edit/{{klass.id}}">編輯</a>&nbsp;&nbsp; <button (click)="onDelete(klass)">刪除</button> </td> ``` ## 功能代碼初始化 klass/index/index.component.ts ``` /** * 刪除班級 * @param klass 班級 */ onDelete(klass: Klass): void { } ``` ## 測試代碼 編寫測試代碼就是我們整體思索功能實現的過程。在軟件開發的過程考慮的因素有三:輸入數據、輸出數據及如何由輸入數據得到輸出數據。其實:輸入數據與輸出數據屬于單元測試主要思索的問題,而如何由輸入數據轉換為輸出數據則需要屬于功能實現代碼主要思索的問題。 | type | name | Description | sample | | --- | --- | --- | --- | | input | click | 點擊刪除按鈕 | | | output | 發起http請求 | Delete /Klass/{id} | Delete /Klass/1 | 有了預期的輸入與輸出后,我們編寫測試代碼如下: klass/index/index.component.spec.ts ``` /** * 模擬返回班級列表 * 找到table中的第二行(第一行為表頭)中的button元素 * 點擊button元素 * 斷言發起了預期的http請求 */ fit('測試刪除按鈕', () => { }); ``` 在注釋中寫代碼編寫流程的過程也就是在模擬我們進行代碼編寫的過程,這很有用(特別是處一些復雜操作時)!接下來,按注釋的思路來完成代碼: ``` /** * 模擬返回班級列表 * 找到table中的第二行(第一行為表頭)中的button元素 * 點擊button元素 * 斷言發起了預期的http請求 */ fit('測試刪除按鈕', () => { const req = httpTestingController.expectOne('http://localhost:8080/Klass?name='); const klasses = [ new Klass(100, '計科1901班', new Teacher(1, 'zhagnsan', '張三')), ]; req.flush(klasses); fixture.detectChanges(); const htmlButtonElement: HTMLButtonElement = fixture.debugElement.query(By.css('table tr td button:first-of-type'?)).nativeElement; expect(htmlButtonElement).toBeDefined();? htmlButtonElement.click(); const req1 = httpTestingController.expectOne('http://localhost:8080/Klass/100'); expect(req1.request.method).toEqual('DELETE'); req1.flush('', {status: 204, statusText: 'No Content'}); httpTestingController.verify(); ① }); ``` * ? 通過CSS選擇器,選擇table中的tr中的td中的第一個button按鈕。 * ? 斷言找到了此按鈕(以防止前面的CSS接寫錯誤)。 * ① 沒有發生預期以外的http請求 #### 測試結果 ``` Chrome 78.0.3904 (Mac OS X 10.13.6) IndexComponent 測試刪除按鈕 FAILED Error: Expected one matching request for criteria "Match URL: http://localhost:8080/Klass/100", found none. ``` ## 功能開發 在編寫測試的同時我們已經將輸入與輸出梳理的很清晰了,接下來的功能開發就顯得簡單了。 klass/index/index.component.ts ``` /** * 刪除班級 * @param klass 班級 */ onDelete(klass: Klass): void { this.httpClient.delete(`http://localhost:8080/Klass/${klass.id}`) .subscribe(() => { alert('刪除成功'); }); } ``` ## 完美化 點擊刪除按鈕并刪除成功后,我們希望在班級列表中同時移除該條記錄。實現該功能的方法最少有兩種解決方案:①刪除成功后調用`ngOnInit`重新拉取后臺數據;②刪除成功的同時,直接將其由C層的班級列表`klasses`屬性中移除; ### ① 重新加載 ``` /** * 刪除班級 * @param klass 班級 */ onDelete(klass: Klass): void { this.httpClient.delete(`http://localhost:8080/Klass/${klass.id}`) .subscribe(() => { this.ngOnInit(); }); } ``` #### 測試 ``` Chrome 78.0.3904 (Mac OS X 10.13.6) IndexComponent 測試刪除按鈕 FAILED Error: Expected no open requests, found 1: GET http://localhost:8080/Klass ``` 該異常是由`httpTestingController.verify();`發出的。當執行`httpTestingController.verify();`時如果還有http請求沒有被我們手動獲取,則會發生異常。 由于我們在刪除成功后執行了`ngOnInit()`,而此方法會重新發起后臺訪問,進而生成了一個新的http請求。所以在`httpTestingController.verify();`時發生了異常。 修正如下: ``` req1.flush('', {status: 204, statusText: 'No Content'}); httpTestingController.expectOne('http://localhost:8080/Klass?name='); ①? httpTestingController.verify(); ``` * ① 刪除成功后又發起了一次初始化請求 ### ② 由數組中移除 ``` /** * 刪除班級 * @param klass 班級 */ onDelete(klass: Klass): void { this.httpClient.delete(`http://localhost:8080/Klass/${klass.id}`) .subscribe(() => { this.klasses.forEach((inKlass, key) => { ① if (klass === inKlass) { this.klasses.splice(key, 1); ② } }); }); } ``` * ① 遍歷教室數組 * ② 遍歷到當前刪除的教室時,將其由數組中移出 #### 測試 ``` req1.flush('', {status: 204, statusText: 'No Content'}); /*重新獲取table中數據,斷言table只有表頭(行數為1)*/ fixture.detectChanges(); const htmlTableElement: HTMLTableElement = fixture.debugElement.query(By.css('table')).nativeElement; expect(htmlTableElement.rows.length).toBe(1); httpTestingController.verify(); ``` # 參考文檔 | 名稱 | 鏈接 | 預計學習時長(分) | | --- | --- | --- | | 源碼地址 | [https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step3.6.1](https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step3.6.1) | - | | CSS選擇器:first-of-type | [https://www.runoob.com/cssref/sel-first-of-type.html](https://www.runoob.com/cssref/sel-first-of-type.html) | 5 | | Test-driven development 測試驅開發 | [Test-driven development 測試驅開發](https://zh.wikipedia.org/wiki/%E6%B5%8B%E8%AF%95%E9%A9%B1%E5%8A%A8%E5%BC%80%E5%8F%91) | 10 | | jasmine null check | [https://www.tutorialspoint.com/jasminejs/jasminejs\_null\_check.htm](https://www.tutorialspoint.com/jasminejs/jasminejs_null_check.htm) | 5 | |HttpTestingController#verify | [https://www.angular.cn/api/common/http/testing/HttpTestingController#verify](https://www.angular.cn/api/common/http/testing/HttpTestingController#verify) | 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>

                              哎呀哎呀视频在线观看