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

                在繼續操作以前,先修正下學生實體。由于我們前面的誤操作,在進行學生實體實始化時,忘記加入班級信息了。下面,我們來共同修正一下學生實體。 norm/entity/student.ts ``` @NgModule({}) export class Student { id: number; klass: Klass; ? name: string; sno: string; constructor(data?: { id?: number; klass?: Klass ?; name?: string; sno?: string }) { if (!data) { return; } this.id = data.id ? data.id : null; this.klass = data.klass ? data.klass : null; ? this.name = data.name ? data.name : ''; this.sno = data.sno ? data.sno : ''; } } ``` * ? 加入了班級信息,幸運的是由于我們使用了更加優化的構造函數。我們剛剛的操作對歷史代碼沒有造成任何影響。 # MVC 在上個小節中,大家使用常規的方法完成新增學生組件的功能,現在我們使用MVC的思想重新開發一遍。 我們剛剛接觸了直接與用戶進行交互的V層 -- add.component.html,以及與V層進行直接交互的C層 -- add.component.ts。按MVC的開發思想:V層負責響應用戶;C層負責接收數據接收、數據較驗、數據轉發;M層則負責邏輯處理。回想下3.6.2刪除班級的小節,我們在后臺進行班級刪除時也正是這么做的。 在使用Angular進行開發時,我們也應該將邏輯處理由C層中剝離出來,進而提升代碼的可讀性、降低軟件維護的成本。當前我們需要一個學生服務來完成學生新增的邏輯功能。 寫代碼之前,我們先簡單畫個圖,這樣自己編寫或是與團隊成員交流的時候會更清晰: ![](https://img.kancloud.cn/56/8e/568e7f03b564e6cd152dbb4fee1e0523_497x304.png) * 方法前面的序號代碼執行順序 * ? 方法名 * ? 輸入參數 * ? 返回值 # M層初始化 與后臺的開發思路一致:我們在app目錄下新建service子目錄 -> 在該目錄下使用angular-cli生成student服務。 ``` panjiedeMac-Pro:app panjie$ mkdir service panjiedeMac-Pro:app panjie$ cd service/ panjiedeMac-Pro:service panjie$ ng g s student CREATE src/app/service/student.service.spec.ts (338 bytes) CREATE src/app/service/student.service.ts (136 bytes) ``` 自動生成的代碼如下所示: ``` import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' ? }) export class StudentService { constructor() { } } ``` ? 聲明被注入范圍為`root`,即整個系統。此時我們可以在整個項目像注入這個服務,比如我們需要在student/add/add.component.ts中注入這個服務,則可以直接在該文件中這么寫: ``` constructor(private studentService: StudentService) { } ``` ## 增加SAVE方法 在MVC的思想,我們將原來在C層中進行的請求后臺的操作轉移到StudentService的save方法中。 service/student.service.ts ``` @Injectable({ providedIn: 'root' }) export class StudentService { constructor(private httpClient: HttpClient) { } /** * 保存學生 * 直接調用HttpClient post方法 * @param student 學生 * @return 此返回值是個可觀察對象: * 1. 其它人可以通過 訂閱 操作來獲取該對象后續發送的值。 * 2. 該對象如果發送值,那么該值的類型必然是Student。 */ save(student: Student): Observable<Student> { const url = 'http://localhost:8080/Student'; return this.httpClient.post<Student?>(url, student); } } ``` 我們往往與新增教師、新增班級時沒有返回值不同,在定義新增學生接口時我們定義了其返回值為Student,也就是說后臺需要將持久化后的學生再返回給前臺。this.httpClient.post的功能是發請一個http post請求,其返回值的類型取決于后臺具體返回的類型,也就是說:該方法的返回類型不定,但必然應該有一個類型(哪怕是void),而泛型就恰到好處的可以實現這一點。我們使用?來規定此this.httpClient.post發送的請求的返回值類型為Student。 ## 單元測試 和測試組件的方法一致,我們來到service/student.service.spec.ts,并對自動生成的文件進行小幅重構。 ``` import {TestBed} from '@angular/core/testing'; import {StudentService} from './student.service'; import {HttpClientTestingModule} from '@angular/common/http/testing'; fdescribe('service -> StudentService', () => { let service: StudentService; ? beforeEach(() => TestBed.configureTestingModule({ imports: [HttpClientTestingModule] ① })); beforeEach(() => { ? service = TestBed.get(StudentService); ? }); it('should be created', () => { expect(service).toBeTruthy(); }); it('save', () => { ? }); }); ``` * ? 將公共的對象(每個測試用例都會用到)向上抽離 * ? 在每個測試用例執行前,本方法內的語句均執行1次 * ? 在每個測試用例前,均重新獲取一個StudentService * ? 新建測試用例測試save方法 ### 完善測試功能 在寫單元測試以前,我們必須要弄清兩個問題:輸入與輸出。在save方法中,輸入的為Teacher,輸出的為一個**可觀察者**。如果想確認這個**可觀察者**發送的數據是否符合我們的預期,則要進行**訂閱**操作。我們按模擬輸入、調用方法、斷言輸出的步驟來編寫以下測試代碼: ``` /** * 測試新增 * 1. 初始化測試數據 * 2. 調用保存方法并進行訂閱 * 2.1 斷言響應中返回了學生ID信息 * 3. 斷言發起了HTTP POST請 * 4. 斷言請求數據 * 5. 模擬HTTP響應數據 * 6. 斷言訂閱的方法被調用 */ it('save', () => { const student: Student = new Student( { name: 'test', klass: new Klass(1, null, null) }); let called = false; service.save(student).subscribe①((returnStudent: Student) => { called = true; ③ expect(returnStudent.id).toBe(-1); }); const httpTestingController: HttpTestingController = TestBed.get(HttpTestingController); const req = httpTestingController.expectOne('http://localhost:8080/Student'); expect(req.request.method).toEqual('POST'); const studentBody: Student = req.request.body.valueOf(); expect(studentBody.name).toEqual(student.name); expect(studentBody.klass.id).toEqual(student.klass.id); req.flush(new Student({id: -1})); ② expect(called).toBe(true); ④ }); ``` * 程序執行順序 ①②③④ ![](https://img.kancloud.cn/dd/93/dd93fb1475906ed9cfbfdcdd89452e80_383x113.png) 測試通過,說明符合預期,M層開發完畢。 # C層 由于StudentService聲明的被注入范圍為root,所以我們可以在直接在student/add/add.component.ts中注入該服務。 ``` export class AddComponent implements OnInit { student: Student; formGroup: FormGroup; constructor(private studentService: StudentService①) { } ``` * ① 和注入其它協作者的方法一樣。 ## 調用M層 然后在需要的方法中直接進行相關調用: student/add/add.component.ts ``` onSubmit(): void { this.student = this.formGroup.value; this.studentService.save(this.student).subscribe★((student: Student) => {① console.log(student); }); } ``` * ★ 必須進行訂閱,否則HttpClient將不會發起POST請求。 HttpClient這個被訂閱者有點意思,它像極了現實社會中的房地產商。幾年前的房地產商拿到地以后,下一步就是做模型畫大餅來告知老百姓:我將要蓋一個什么樣的房子,然后價格是多少。如果沒有用戶愿意購買,那么前面就會一直停留計劃之中;只有當用戶真真切切的交了錢,房地產商才會真真正正的去蓋樓。現實社會中這無形的助長了地價的飆升,增加了購房人面臨的延期交房或是不交房的風險。但在計算機的世界時,這卻不失為一種最佳的解決方案:HttpClinet.post方法只是表明將進行一個post請求,而如果沒有人想知道請求結果的話,那我一直不會發起真實請求,只有當有人訂閱了它表明想獲取請求結果時,它才會真真切切的去發起這個HTTP請求。所以如果要保證該請求真實發生,必須對其進行訂閱。 ## 單元測試 由于我們在組件中引入了UserService,而在UserService又引入了HttpClient,所以執行原單元測試將會報沒有HttpClient的提供者錯誤。 ![](https://img.kancloud.cn/7f/f5/7ff5e631195a4f088e5bcddbdf60634c_194x292.png) ``` NullInjectorError: StaticInjectorError(DynamicTestModule)\[HttpClient\]: StaticInjectorError(Platform: core)\[HttpClient\]: NullInjectorError: No provider for HttpClient! ``` 與解決其它的此類問題的方法相同,我們在單元測試中引入HttpClientTestingModule。 ``` beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [AddComponent], imports: [ ReactiveFormsModule, HttpClientTestingModule ① ], }) .compileComponents(); })); ``` ### 增加測試內容 為了確保當用戶添加寫相關的內容后點擊保存按鈕發起我們預期的請求,我們對以前的測試代碼進行以下補充。 ``` /** * 1. 向表單中輸入值 * 2. 點擊保存按鈕 * 3. 斷言輸入的值傳入到了C層 */ fit('should create', () => { expect(component).toBeTruthy(); formTest.setInputValue('input[name="name"]', 'testname'); formTest.setInputValue('input[name="sno"]', 'testno'); formTest.clickButton('button[type="submit"]'); fixture.detectChanges(); expect(component.student.name).toEqual('testname'); expect(component.student.sno).toEqual('testno'); savePostTest(); ① }); /** * 斷言發起了相關請求 * 斷言在請求的中接收到了對應的值 */ const savePostTest = (): void => { ② const httpTestingController: HttpTestingController = TestBed.get(HttpTestingController); const req = httpTestingController.expectOne('http://localhost:8080/Student'); expect(req.request.method).toEqual('POST'); const student: Student = req.request.body.valueOf(); expect(student.name).toEqual('testname'); expect(student.sno).toEqual('testno'); }; ``` 由于我們在組件中訂閱返回內容后,僅僅是進行控制臺打印操作,所以未對組件訂閱后的內容進行測試。 ![](https://img.kancloud.cn/c0/f0/c0f0656945a7881b4e285e539af8da9c_790x116.png) 測試通過。 # 加入選擇班級組件 最后讓我們加入選擇班級組件 ## V層 student/add/add.component.html ``` <label>班級:<app-klass-select (selected)="onSelectKlass($event)"></app-klass-select></label> ``` ## C層 student/add/add.component.ts ``` export class AddComponent implements OnInit { ... klass: Klass; ? constructor(private studentService: StudentService) { } ngOnInit() { this.student = new Student(); this.formGroup = new FormGroup({ name: new FormControl(''), sno: new FormControl('') }); } onSelectKlass(klass: Klass): void { ? this.klass = klass; } onSubmit(): void { this.student = this.formGroup.value; this.student.klass = this.klass; ? this.studentService.save(this.student).subscribe((student: Student) => { console.log(student); }); } ``` ## 單元測試 在當前的測試思路下,初始化單元測試時必須先要弄清各個模塊前的依賴關系。 ![](https://img.kancloud.cn/27/20/27205f7bfc69c662809faca688b9f03a_356x370.png) 按上述依賴圖,我們需要如下定制測試文件: ``` beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [AddComponent, KlassSelectComponent①], imports: [ ReactiveFormsModule, HttpClientTestingModule, CoreModule ② ], }) .compileComponents(); })); ``` * ① 同模塊的該組件加入到declarations中 * ② 不同模塊的將組件所在的模塊加入到imports中 ### 增加測試內容 student/add/add.component.spec.ts ``` expect(component).toBeTruthy(); component.klass = new Klass(-1, null, null); ? formTest.setInputValue('input[name="name"]', 'testname') ... const savePostTest = (): void => { ... expect(student.sno).toEqual('testno'); expect(student.klass.id).toEqual(-1); ? }; ``` ![](https://img.kancloud.cn/12/ad/12ad9f23d9a26daabd4ac00c9d491b89_410x138.png) ## 整體測試 最后,將特定方法上的`f`去除,進行整個項目的單元測試. ![](https://img.kancloud.cn/11/46/1146b85e30d0d3d083e2c4ed8ea20321_462x153.png) # 參考文檔 | 名稱 | 鏈接 | 預計學習時長(分) | | --- | --- | --- | | 源碼地址 | [https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step4.5.5](https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step4.5.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>

                              哎呀哎呀视频在线观看