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

                實踐是檢驗真理的唯一標準。無論什么樣的選手,單元測試做的再好也難免會有想不到的地方。此時便需要集成測試來補刀了。什么是集成測試呢?簡單來說就是把幾個小的模塊組裝到一起,或是把一些單元測試的小的粒度組合到一起進行測試。最簡單最不可靠的集成測試的方法便是本教程中采用的:人為驗證法。angular其實為我們提供了強大的集成測試工具,在angular中又被稱為`端對端的測試`,即` End to end `,由于英文的`2(two)`與`to`同音,所以又被稱為`e2e`。在angular的根目錄中為我們內置了e2e的測試樣例,我們可以通過`ng e2e`或`ng e`來啟動它們。 > 你可能由于網絡原因導該啟動不成功,具體可參考:[https://segmentfault.com/a/1190000021216402](https://segmentfault.com/a/1190000021216402)解決。 e2e測試和單元測試不同,單元測試需要關注代碼的執行情況,需要對變量的值,是否按我們預期的參數進行調用,函數的返回值等分別進行斷言。而e2e測試僅停留在界面上,通過模擬打開某個地址,模擬人為的操作最后斷言界面會產生什么樣的效果。受篇幅、教程難度設計及筆者水平的限制,在本教程中只求帶領大家接觸了解下更自動化的e2e測試,力求起到拋磚引玉的效果。 # 了解e2e測試 首次啟動e2e時,其將為我們下載最新的驅動,此過程的耗時取決于我們的網絡速度。e2e測試正式啟動后,將查找項目根目錄下的e2e文件夾下的src文件夾下的以`.e2e-sppec.ts`結尾的文件,比如angular在初始化時為我們自動生成的`app.e2e-spec.ts` ![](https://img.kancloud.cn/b2/41/b241c440e3935cb02ab8bc8f44e050b4_408x257.png) 在angular為我們生成的示例中`app.po.ts`及`app.e2e-spec.ts`兩個文件相互配合使用:`app.po.ts`用于獲取(設置)應用的值,而`app.e2e-spec.ts`則負責斷言。 e2e/src/app.po.ts ``` import { browser, by, element } from 'protractor'; export class AppPage { ① navigateTo() { ② return browser.get(browser.baseUrl) as Promise<any>; ③ } getTitleText() { return element(by.css('app-root .content span')).getText() as Promise<string>; ④ } } ``` * ① 定義類名并export(否則其它文件無法調用它,也就失去了配合`app.e2e-spec.ts`的作用 * ② 定義方法 * ③ 打開瀏覽器首頁 * ④ 通過css選擇器,找到`app-root .content span`的`text 文本值` e2e/src/app.e2e-spec.ts ``` import { AppPage } from './app.po'; import { browser, logging } from 'protractor'; describe('workspace-project App①', () => { let page: AppPage; beforeEach(() => { ② page = new AppPage(); }); it('should display welcome message', () => { ③ page.navigateTo(); ④ expect(page.getTitleText()).toEqual('web-app app is running!'); ⑤ }); afterEach(async? () => { ? // Assert that there are no errors emitted from the browser const logs = await browser.manage().logs().get(logging.Type.BROWSER); ? expect(logs).not.toContain(jasmine.objectContaining({ ? level: logging.Level.SEVERE, } as logging.Entry)); }); }); ``` * ① 與單元測試一樣,為此測試啟個好記的名字 * ② 每次執行測試用例前,執行1次本方法 * ③ 測試用例 * ④ 調用輔助類中的方法,打開應用首頁 * ⑤ 斷言輔助類獲取的頁面元素內容為`web-app app is running!` * ? 在每個測試用例執行完畢后執行 * ? 聲明該測試用異步測試 * ? 獲取瀏覽器報的錯誤 * ? 斷言錯誤列表中的錯誤類型沒有`logging.Level.SEVERE` 由于我們的項目早已不是angular生成的原始項目,所以在運行此測試時會發生如下錯誤: ![](https://img.kancloud.cn/55/37/5537d85dea35319835724ff348369f71_1249x151.png) 它在說通過相應的CSS選擇器未找到任何的元素,我們對應修正為: e2e/src/app.po.ts ``` getTitleText() { return element(by.css('app-root .content span')).getText() as Promise<string>; ? return element(by.css('app-welcome h1')).getText() as Promise<string>; ? } ``` e2e/src/app.e2e-spec.ts ``` import {browser, logging} from 'protractor'; ... it('should display welcome message', () => { browser.sleep(1000); ? page.navigateTo(); browser.sleep(2000); ? expect(page.getTitleText()).toEqual('web-app app is running!'); ? expect(page.getTitleText()).toEqual('歡迎使用河北工業大學教務管理系統'); ? browser.sleep(2000); ? }); ``` * ? 為了更清晰的觀察測試執行的過程,每執行1步我們讓瀏覽器小睡一會 最后打開終端進入項目文件夾,執行`ng e2e`來啟動集成測試,網絡暢通的情況下我們將看到此測試自動打開chrome執行相應的測試程序,測試完成后主動的半閉瀏覽器以結束測試。 ![](https://img.kancloud.cn/2e/8d/2e8d9b7a30ca8dd2588097f8f2e9b204_499x211.png) 了解過程到此結束,如果你感覺到意猶未盡,可以來到protractortest的[官網](https://www.protractortest.org/#/)進一步地學習。 # 添加路由 與添加其它的路由相同,打開student模塊的路由文件student-routing.module.ts,并添加如下路由: ``` const routes: Routes = [ { path: 'add', component: AddComponent }, { ? path: '', component: IndexComponent } ]; ``` # 添加菜單項 繼續打開nav組件,在菜單中添加'學生管理'菜單項: nav/nav.component.ts ``` ngOnInit() { this.title = '教務管理系統'; this.menus.push({url: 'teacher', name: '教師管理'}); this.menus.push({url: 'klass', name: '班級管理'}); this.menus.push({url: 'student', name: '學生管理'}); ? } ``` 使用`ng serve --open`啟動應用,啟動瀏覽器控制臺,點擊`學生管理`菜單并查看報錯信息: ![](https://img.kancloud.cn/47/0f/470fa47ceaea3c251ede48059ea7167e_1042x116.png) # 添加依賴 按錯誤提示增加student模擬的依賴: student/student.module.ts ``` @NgModule({ declarations: [AddComponent, KlassSelectComponent, IndexComponent], imports: [ CommonModule, StudentRoutingModule, ReactiveFormsModule, FormsModule, ? CoreModule ] }) export class StudentModule { } ``` 點擊測試: ![](https://img.kancloud.cn/fb/70/fb70cd838287aaf654f4edc093394fc5_910x259.png) 錯誤類型為網絡錯誤,此時便可以啟動后臺來進一步進行其它功能的驗證了。 # 啟動后臺 使用你最喜歡的方式來啟動后臺。 # 測試并修正其它內容 在分模塊開發的情況下,若想保障各個模塊間的有效聯通是比較困難的事情。由于在開發過程中每個模塊將分配給不同的團隊成員開發,所以學生列表組件與新增學生組件可能是同步開發的。而各個模塊間的正常跳轉的前提則是:預跳轉的模塊是存在的。 ## index組件 -> 新增組件 student/index/index.component.html ``` ... </form> <div class="row"> <div class="col text-right"> <a class="btn btn-primary" routerLink="./add">新增學生</a> </div> </div> <table> ... ``` 接下來按 新增教師 -> 新增班級 -> 新增學生的順序測試添加學生功能: ![](https://img.kancloud.cn/8c/0c/8c0c53c0084b3008d50c71992d718537_772x346.gif) 測試過程中我們發現以下問題: * 標題應該由 編輯教師 修正為 新增學生 * 新增學生完成后,點擊保存按鈕,界面未跳轉 * 學生管理列表組件的 table 沒有添加bootstrap樣式 ## 修正標題 請自行將新裝學生組件中 編輯老師 修正為 新增學生 ## 新增組件 -> index組件 新增組件中點擊保存按鈕后,應該成功跳轉到index界面,請自動完成 ## 增加bootstrap樣式 為index組件的table增加bootstrap樣式,請自行完成 # 功能測試 完成了基礎樣式修正后,開發進行功能測試。 ## 綜合查詢 綜合查詢主要對姓名、學號、班級進行查詢,要使測試正常進行,則需要準備不同姓名、不同學號、不同班級的學生。 ![](https://img.kancloud.cn/04/cd/04cd1f73d62f3fb205e356f3cb6d0e75_1138x219.png) 測試如下: ![](https://img.kancloud.cn/dd/d4/ddd41bfbf1a3949e42cd481f56baeeb3_950x297.gif) ## 全選、單選 ![](https://img.kancloud.cn/f9/03/f90338944145048d86570b1caddc62fb_878x193.gif) ## 分頁 成功的測試分頁,則需要不少于5頁的學生數據,在開始測試前先新增多條測試學生: ![](https://img.kancloud.cn/5d/47/5d47ed6582714f3a7c69c4eb88685bc2_876x122.png) 當前共7頁數據,開始進行測試 ![](https://img.kancloud.cn/45/57/4557db11d810cb7169d7fb1b0a2e34f9_878x193.gif) 發現兩個問題: * 頁碼應該為1基,實際卻為0基 * 點擊其它頁碼時卻跳轉到了首頁 第一個問題修正相對簡單,請自行完成。 第二個問題是由于我們在頁碼中使用了`a`標簽,然后`a`標簽中定義了`href="#"`引起的。在一般的WEB應用中,我們習慣性的使用`href="#"`來表示當點擊該a標簽時不進行任何跳轉。但在`single page web application(SPA) 單頁面WEB應用`中就不一樣了。在單頁面WEB應用中,雖然瀏覽器的導航欄也會按照用戶的點擊進行變更,但卻并沒有重新發起頁面加載請求。這種變更是通過調用瀏覽器相關的API來實現的,而非用戶點擊了需要跳轉的a標簽。在學生管理中,雖然瀏覽器顯示的地址為`localhost/student`但angular很清晰的明了:當前系統的實際請求地址為:`localhost`,遇到`href="#"`實際對應的地址應該為:`localhost/#`。而我們所期待的卻是`localhost/student/#`。猜出了原因,那么解決方案也就隨著而來了。 即然不能使用`href="#"`,那么我們將其刪除好了: student/index/index.component.html ``` <ul class="pagination"> <li class="page-item" [ngClass]="{'disabled': params.page === 0}" (click)="onPage(0)"> <span class="page-link">首頁</span> </li> <li class="page-item" [ngClass]="{'disabled': params.page === 0}" (click)="onPage(params.page - 1)"> <span class="page-link">上一頁</span> </li> <li class="page-item" [ngClass]="{'active': params.page === page}" *ngFor="let page of pages" (click)="onPage(page)"> <a class="page-link" *ngIf="page !== params.page">{{page + 1}}</a> <span class="page-link" *ngIf="page === params.page">{{page + 1}}<span class="sr-only">(current)</span></span> </li> <li class="page-item" [ngClass]="{'disabled': params.page === pageStudent.totalPages - 1}" (click)="onPage(params.page + 1)"> <a class="page-link">下一頁</a> </li> <li class="page-item" [ngClass]="{'disabled': params.page === pageStudent.totalPages - 1}" (click)="onPage(pageStudent.totalPages - 1)"> <a class="page-link">尾頁</a> </li> </ul> ``` ![](https://img.kancloud.cn/db/d5/dbd5a56d6bee8482a6ca47065c72ef76_878x193.gif) 刪除`href="#"`后的確修正了前面的跳轉首頁問題,但分頁表現卻并不完美。三個新問題又被暴露了出來: * 首頁 上一頁 的樣式是我們期望的,但其它頁碼的樣式卻不行。 * 以前將鼠標移到分頁按鈕上的時候,會有個 小手 出現,現在沒有了。 * 當前頁為第1頁時,點擊上一頁仍生效 * 當前頁為最后1頁時,點擊下一頁仍生效 下面分別對上述問題進行修正: ### 樣式問題 其它頁碼的樣式不同于首頁、上一頁是由于我們在首頁、上一頁中使用為`span`標簽,也在頁碼中使用的`a`標簽。使用`a`標簽有個默認的好處:當`a`標簽存在`href`屬性時,鼠標移上去將自動變成 小手 的樣子。為了使格式統一,首先將`a`標簽全部換成`span`標簽。 student/index/index.component.html ``` <ul class="pagination"> <li class="page-item" [ngClass]="{'disabled': params.page === 0}" (click)="onPage(0)"> <span class="page-link">首頁</span> </li> <li class="page-item" [ngClass]="{'disabled': params.page === 0}" (click)="onPage(params.page - 1)"> <span class="page-link">上一頁</span> </li> <li class="page-item" [ngClass]="{'active': params.page === page}" *ngFor="let page of pages" (click)="onPage(page)"> <span class="page-link" *ngIf="page !== params.page">{{page + 1}}</span> <span class="page-link" *ngIf="page === params.page">{{page + 1}}<span class="sr-only">(current)</span></span> </li> <li class="page-item" [ngClass]="{'disabled': params.page === pageStudent.totalPages - 1}" (click)="onPage(params.page + 1)"> <span class="page-link">下一頁</span> </li> <li class="page-item" [ngClass]="{'disabled': params.page === pageStudent.totalPages - 1}" (click)="onPage(pageStudent.totalPages - 1)"> <span class="page-link">尾頁</span> </li> </ul> ``` ![](https://img.kancloud.cn/fc/eb/fceb9d8c559b47fb42a5bdf88f800732_505x67.png) 瀏覽器默認為有`href`屬性的`a`標簽的`hover`違類上添加了`cursor: pointer`屬性,以使得鼠標移動到元素上變成 小手 的樣子。如果想用`span`標簽添加此屬性,則為其`hover`違類添加對應的樣式即可: student/index/index.component.sass ``` ul.pagination > li > span:hover cursor: pointer ``` ![](https://img.kancloud.cn/a0/78/a07882beed563c2e4c9eca9bc2c5f695_878x193.gif) pointer常用于跳轉的鏈接,分頁功能中我們更習慣于使用default( 默認光標(通常是一個箭頭))。 student/index/index.component.sass ``` ul.pagination > li > span:hover cursor: default ``` ### 上一頁、下一頁 此時我有了一個疑問,明明下一頁、上一頁顯示為灰色,卻為什么還能點擊呢?這時候就需要看看angular為我們生成的頁面源碼了: ![](https://img.kancloud.cn/23/10/2310ea027a670c65670b57a4e2bfeed0_855x50.png) 在代碼中使用`[ngClass]="{'disabled': params.page === 0}"`來控制`disabled`時,其實angular是為該元素在`params.page === 0`時添加了一個`disabled`樣式,使其看起來是不能夠點擊的。要使一個元素真正的不能夠被點擊,前提是該元素具有天然的`disabled`屬性,比如`button`元素就具有這個屬性。實驗如下: student/index/index.component.html ``` <li class="page-item" [ngClass]="{'disabled': params.page === 0}" (click)="onPage(params.page - 1)"> <span class="page-link">上一頁</span> </li> <button [disabled]="params.page === 0" (click)="onPage(params.page - 1)">上一頁</button> ``` ![](https://img.kancloud.cn/fa/75/fa75d2503394fb3a49a7f4e2b8c879ef_878x77.gif) 實驗得出:天然有`disabled`屬性的button,可以設置其`disabled`屬性,能起到禁止點擊的作用。而天然并沒有`disabled`屬性的li,則只能是看起來`disabled`不能被點擊了,而實際上點用戶點擊該元素時,仍然能夠觸發其綁定的onPage方法。對于C層的onPage方法而言,如果按正常的邏輯,是不應該接收到小于0或是大于等于總頁數的值的,但程序的魅力就是如此:它總能找點小樂子,讓本來應該的事情變得那么的不應該。既然V層解決不了(在V層中也可以考慮將li變為button標簽,但這將破壞了原bootstrap結構而使得該分頁樣式變得難以維護),那就在C層的onPage方法上下功夫吧。 student/index/index.component.ts ``` /** * 點擊分頁按鈕 * @param page 要請求的頁碼 */ onPage(page: number) { if (page < 0 || page >= this.pageStudent.totalPages) { ? return; } this.params.page = page; this.loadData(); } ``` 對應修正單元測試: student/index/index.component.spec.ts ``` fit('onPage 功能測試', () => { spyOn(component, 'loadData'); component.params.page = 4; component.onPage(3); expect(component.params.page).toEqual(3); expect(component.loadData).toHaveBeenCalled(); /* 越界測試:期望不改變當前頁碼值,loadData僅被前面的代碼調用了1次(本次未調用)*/ component.onPage(-1); expect(component.params.page).toEqual(3); expect(component.loadData).toHaveBeenCalledTimes(1); /* 越界測試:期望不改變當前頁碼值,loadData僅被前面的代碼調用了1次(本次未調用)*/ component.pageStudent.totalPages = 5; component.onPage(5); expect(component.params.page).toEqual(3); expect(component.loadData).toHaveBeenCalledTimes(1); }); ``` 此時當頁碼為首、尾頁時,再次點擊上一頁下一頁時便不會發生越界的情況了,至此集成測試完畢。 # 總結 基于**人是必然會犯錯誤的**的理論,在開發時引入單元測試來對自己的代碼進行功能性驗證,從而降低犯錯誤的概率。同樣基于該理論,當各個模塊分離完成后進行組合的測試來進一步降低犯錯的概率。所以的準備工作,都是為了最終減少生產環境中可能面臨的用戶各種無厘頭以及**非常正常**的操作。我們夢想并努力著:在應用上線的那一天,我們可以關上手機心無旁騖地躺在床上休息。這應該就是軟件工程的初心吧。 # 參考文檔 | 名稱 | 鏈接 | 預計學習時長(分) | | --- | --- | --- | | 源碼地址 | [https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step4.6.10](https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step4.6.10) | - |
                  <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>

                              哎呀哎呀视频在线观看