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

                # 初始單元測試 原型永遠是第一位的!本節中我們快速的對登錄組件進行初始化,并嘗試使用代碼來測試登錄按鈕的綁定狀態。 ## 初始化 打開shell并進行`src/app`文件夾,使用`ng g c login`初始化登錄組件: ```bash panjiedeMacBook-Pro:app panjie$ ng g c login CREATE src/app/login/login.component.css (0 bytes) CREATE src/app/login/login.component.html (20 bytes) CREATE src/app/login/login.component.spec.ts (619 bytes) CREATE src/app/login/login.component.ts (271 bytes) UPDATE src/app/app.module.ts (806 bytes) ``` 然后,我們參考[bootstrap示例登錄界面](https://getbootstrap.com/docs/5.0/forms/overview/)對原型初始化如下: ```html <form class="container-sm"> <div class="mb-3"> <label for="username" class="form-label">用戶名</label> <input type="text" class="form-control" id="username" aria-describedby="usernameHelp"> <div id="usernameHelp" class="form-text">我們不會分享你的登錄信息</div> </div> <div class="mb-3"> <label for="exampleInputPassword1" class="form-label">密碼</label> <input type="password" class="form-control" id="exampleInputPassword1"> </div> <button type="submit" class="btn btn-primary">登錄</button> </form> ``` ## 屬性與方法 ```typescript +++ b/first-app/src/app/login/login.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import {Component, OnInit} from '@angular/core'; @Component({ selector: 'app-login', @@ -6,10 +6,18 @@ import { Component, OnInit } from '@angular/core'; styleUrls: ['./login.component.css'] }) export class LoginComponent implements OnInit { + teacher = {} as { + username: string, + password: string + }; - constructor() { } + constructor() { + } ngOnInit(): void { } + onSubmit(): void { + console.log('點擊了登錄按鈕'); + } } ``` V層綁定: ```html +++ b/first-app/src/app/login/login.component.html @@ -1,12 +1,14 @@ -<form class="container-sm"> +<form class="container-sm" (ngSubmit)="onSubmit()"> <div class="mb-3"> <label for="username" class="form-label">用戶名</label> - <input type="text" class="form-control" id="username" aria-describedby="usernameHelp"> + <input type="text" class="form-control" id="username" aria-describedby="usernameHelp" + [(ngModel)]="teacher.username" name="username"> <div id="usernameHelp" class="form-text">我們不會分享你的登錄信息</div> </div> <div class="mb-3"> <label for="exampleInputPassword1" class="form-label">密碼</label> - <input type="password" class="form-control" id="exampleInputPassword1"> + <input type="password" class="form-control" id="exampleInputPassword1" + [(ngModel)]="teacher.password" name="password"> </div> <button type="submit" class="btn btn-primary">登錄</button> </form> ``` ## 測試 軟件工程相比于交通、土木工程等其它實體工程有著先天的優勢 ---- 幾乎可以忽略不計的測試成本。所以我們在開發中,要摒棄**我認為**、**應該**等字眼,當不太清楚自己的代碼是否正確運行時,最簡單的方法就是測試一下。 加入測試代碼: ```html +++ b/first-app/src/app/login/login.component.html @@ -1,3 +1,4 @@ +{{teacher | json}} <form class="container-sm" (ngSubmit)="onSubmit()"> <div class="mb-3"> <label for="username" class="form-label">用戶名</label> ``` 在單元測試中: 1. 加入FormModule以支持`[(ngModel)]` 2. 啟用自動檢測變更以便捷觀察數據的時實變更情況 ```typescript +++ b/first-app/src/app/login/login.component.spec.ts @@ -1,6 +1,7 @@ import {ComponentFixture, TestBed} from '@angular/core/testing'; import {LoginComponent} from './login.component'; +import {FormsModule} from '@angular/forms'; fdescribe('LoginComponent', () => { let component: LoginComponent; @@ -8,7 +9,10 @@ fdescribe('LoginComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [LoginComponent] + declarations: [LoginComponent], + imports: [ + FormsModule + ] }) .compileComponents(); }); @@ -21,5 +25,6 @@ fdescribe('LoginComponent', () => { it('should create', () => { expect(component).toBeTruthy(); + fixture.autoDetectChanges(); }); }); ``` 使用`ng t `快速啟動組件: ![image-20210303081750151](https://img.kancloud.cn/fd/1e/fd1e172d6eb549ff6eda09185197f3a9_1624x782.png) - 測試一:數據綁定成功 - 測試二:按鈕綁定生效 ## 單元測試 在一般的項目中,用人眼來對代碼進行測試是不可靠的。它的不可靠主要體現在兩個方面: * 隨著組件功能的增多,人眼同時檢測多種測試信息,免不了顧此失彼。開發了一個新功能同時,也可能破壞了一個原有的正常的功能。 * 由于**應該看什么**并沒有形成文檔。和合作開發中,張三在接手了李四的組件后,完全不知道應該看什么,哪是對的,哪又是錯的。 鑒于此,我們可以采用使用代碼來測試代碼的方法,由于這種方法是針對功能點的某個小的功能單元進行測試,所以又被稱為**單元測試**,英文關鍵字為**Unit Test**。 在此,我們簡單介紹下如何使用單元測試來驗證登錄按鈕與C層的`onSubmit`方法是否綁定成功。 ### 流程 此測試在思想上大概分為以下幾步: 1. 獲取V層中的登錄按鈕 2. 使用代碼來點擊這個按鈕 3. 查看C層中的方法是否被觸發 接下來,我們分別介紹上述步驟的實現方法。 ### 獲取V層的登錄按鈕 在單元測試中,我們可以使用`fixture`來獲取組件V層相關的數據,比如可以使用如下代碼來獲取當前V層對應的`dom`節點: ```typescript +++ b/first-app/src/app/login/login.component.spec.ts @@ -26,5 +26,7 @@ fdescribe('LoginComponent', () => { it('should create', () => { expect(component).toBeTruthy(); fixture.autoDetectChanges(); + + console.log(fixture.elementRef.nativeElement); }); }); ``` 來到控制臺點擊對應的日志內容,對應當前組件的`dom`。 ![image-20210303083131752](https://img.kancloud.cn/0b/13/0b137c31425cbba5a85a4298e598ad1c_2566x904.png) HTML中的每個元素都對應一個對象,該對象被稱為**文檔對象模型(Document Object Model)**。我們使用JS來操作HTML頁面的便是通過操作這個**文檔對象模型**。這樣一來,相較于傳統的直接編寫html代碼,網頁的生成便又多了一種方法:javascript 操作 dom。 獲取到組件dom后,我們在根據`dom`知識來獲取當前`dom`下的子`dom` ---- 登錄按鈕。 ```typescript +++ b/first-app/src/app/login/login.component.spec.ts @@ -2,6 +2,7 @@ import {ComponentFixture, TestBed} from '@angular/core/testing'; import {LoginComponent} from './login.component'; import {FormsModule} from '@angular/forms'; +import {root} from 'rxjs/internal-compatibility'; fdescribe('LoginComponent', () => { let component: LoginComponent; @@ -28,5 +29,8 @@ fdescribe('LoginComponent', () => { fixture.autoDetectChanges(); console.log(fixture.elementRef.nativeElement); + const rootDivElement = fixture.elementRef.nativeElement as HTMLDivElement; ?? + const submitButtonElement = rootDivElement.querySelector('button') as HTMLButtonElement; ?? + console.log(submitButtonElement); }); }); ``` - 根據實際情況,使用`as`來為變量指定一個類型。 ?? ## 使用代碼點擊登錄按鈕 使用代碼對按鈕進行點擊非常簡單,僅僅需要調用該對象的`click()`方法即可: ```typescript +++ b/first-app/src/app/login/login.component.spec.ts @@ -32,5 +32,7 @@ fdescribe('LoginComponent', () => { const rootDivElement = fixture.elementRef.nativeElement as HTMLDivElement; const submitButtonElement = rootDivElement.querySelector('button') as HTMLButtonElement; console.log(submitButtonElement); + + submitButtonElement.click(); }); }); ``` 此時在控制臺中成功的打印了相關日志,說明C層的`onSubmit`方法被成功的觸發了,從而證明了綁定是成功的。 ![image-20210303084535974](https://img.kancloud.cn/c1/1f/c11f9c292a8c75d8323a60d695312a93_998x204.png) 上述過程中,我們成功的實現了:使用代碼來點擊**登錄按鈕**,但在最后的驗證環節仍然是使用人眼進行觀察的,這仍然沒有消除人眼觀察的**不可靠性**。 ### 驗證C層方法被觸發 遺憾的是,除了觀察,我們是沒有辦法直接驗證某個C層的方法是否被成功的調用的。為了實現這種驗證,我們采用:建造模擬C層的方法來間接達到這個目的。 所謂的模擬C層,就是在根據當前的C層,建立一個外表看起來一模一樣的C層。原C層有什么方法,我們的模擬C層就會有什么方法;原C層的方法中有什么樣的參數,我們的模擬C層的方法中也會有什么參數。 如果我們僅僅是為了驗證某一個方法,則還可以在這個方法上安排一個間諜。這像極了我們在電視劇中看到的諜戰片。為了獲取一手的情況,我們在在敵方的情報部門安排一個間諜。此時,敵方在我方間諜發送信息時,實際的信息卻被我方獲取了。 Jasmie提供的`spyOn`方法提供了這種放置間諜的功能: ```typescript +++ b/first-app/src/app/login/login.component.spec.ts @@ -32,7 +32,8 @@ fdescribe('LoginComponent', () => { const rootDivElement = fixture.elementRef.nativeElement as HTMLDivElement; const submitButtonElement = rootDivElement.querySelector('button') as HTMLButtonElement; console.log(submitButtonElement); - + + spyOn(component, 'onSubmit'); submitButtonElement.click(); }); }); ``` 此時再次運行單元測試代碼,發現控制臺的日志不見了: ![image-20210303085859717](https://img.kancloud.cn/47/a7/47a7dd291a2285d71b538b60990ecbad_1024x220.png) 為了近一步確認的確是間諜方法被調用了,我們還可以補充下`spyOn`方法: ```typescript +++ b/first-app/src/app/login/login.component.spec.ts @@ -33,7 +33,7 @@ fdescribe('LoginComponent', () => { const submitButtonElement = rootDivElement.querySelector('button') as HTMLButtonElement; console.log(submitButtonElement); - spyOn(component, 'onSubmit'); + spyOn(component, 'onSubmit').and.callFake(() => console.log('間諜方法被調用')); submitButtonElement.click(); }); }); ``` ![image-20210303090642492](https://img.kancloud.cn/a1/d1/a1d1cc75da6ae3d8da4bc421096a2492_1012x186.png) 最后,我們加入以下驗證代碼,來間諜來替我們驗證`onSubmit`方法的確是被調用了。 ```typescript +++ b/first-app/src/app/login/login.component.spec.ts @@ -34,6 +34,10 @@ fdescribe('LoginComponent', () => { console.log(submitButtonElement); spyOn(component, 'onSubmit').and.callFake(() => console.log('間諜方法被調用')); + // 點擊按鈕以前,onSubmit方法應該被調用了0次。 + expect??(component.onSubmit).toHaveBeenCalledTimes(0); submitButtonElement.click(); + // 點擊按鈕以后,onSubmit方法應該被調用了1次。 + expect??(component.onSubmit).toHaveBeenCalledTimes(1); }); }); ``` **expect**??代表期望,就是說我們預計組件的方法是被調用了0次或是1次,如果實際的情況與我們預計的相同,則該代碼將正確運行;如果實際情況與我們預計的不同,則該處代碼將會觸發異常。 ![image-20210303091229639](https://img.kancloud.cn/0c/c1/0cc147be04505a559a30b4e7716566c8_1244x344.png) 此時,如果我們刪除V層中關于觸發C層的代碼,則會得到如下異常。 ![image-20210303091548090](https://img.kancloud.cn/7a/71/7a716ba791bb73fcb318b05a1a771510_1032x152.png) 該異常提示我們:點擊了V層的登錄按鈕后,并沒有調用C層的`onSubmit`方法。而這種情況是不正確的。 ## 本節作業 嘗試刪除V層中關于觸發C層的代碼來觸發異常。 | 名稱 | 地址 | 備注 | | ----------------------- | ------------------------------------------------------------ | --------------------------- | | html dom | [https://www.runoob.com/htmldom/htmldom-tutorial.html](https://www.runoob.com/htmldom/htmldom-tutorial.html) | | | HtmlDivElement | [https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLElement](https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLElement) | 建議將語言切換為English查看 | | HtmlButtonElement | [https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLButtonElement](https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLButtonElement) | 建議將語言切換為English查看 | | Element.querySelector() | [https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelector](https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelector) | 看不太懂時,再切回中文查看 | | click() | [https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click) | | | spyOn | [https://jasmine.github.io/api/edge/global.html#spyOn](https://jasmine.github.io/api/edge/global.html#spyOn) | | | Expect | [https://jasmine.github.io/api/edge/global.html#expect](https://jasmine.github.io/api/edge/global.html#expect) | | | 本節源碼 | [https://github.com/mengyunzhi/angular11-guild/archive/step3.1.zip](https://github.com/mengyunzhi/angular11-guild/archive/step3.1.zip) | |
                  <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>

                              哎呀哎呀视频在线观看