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

                本節由M層到C層完成功能性代碼的書寫。 ## M層 ``` panjiedeMac-Pro:service panjie$ ng g s course CREATE src/app/service/course.service.spec.ts (333 bytes) CREATE src/app/service/course.service.ts (135 bytes) ``` 功能性代碼: service/course.service.ts ```typescript import {Injectable} from '@angular/core'; import {Course} from '../norm/entity/course'; import {Observable} from 'rxjs'; import {HttpClient} from '@angular/common/http'; @Injectable({ providedIn: 'root' }) export class CourseService { private url = 'http://localhost:8080/Course'; constructor(private httpClient: HttpClient) { } /** * 保存課程 * @param course 課程 */ save(course: Course): Observable<Course> { return this.httpClient.post<Course>(this.url, course); } } ``` ### 單元測試 service/course.service.spec.ts ```javascript import {TestBed} from '@angular/core/testing'; import {CourseService} from './course.service'; import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing'; import {Course} from '../norm/entity/course'; describe('CourseService', () => { beforeEach(() => TestBed.configureTestingModule({ imports: [ HttpClientTestingModule ] })); it('should be created', () => { const service: CourseService = TestBed.get(CourseService); expect(service).toBeTruthy(); }); fit('save', () => { const service: CourseService = TestBed.get(CourseService); const testController = TestBed.get(HttpTestingController) as HttpTestingController; // 調用save方法被接收返回數據 const course = new Course(); let result: Course; service.save(course).subscribe((data) => { result = data; }); // 斷言請求符合預期 const request = testController.expectOne('http://localhost:8080/Course'); expect(request.request.method).toEqual('POST'); expect(request.request.body).toEqual(course); // 數據返回符合預期 const returnCourse = new Course(); request.flush(returnCourse); expect(returnCourse).toBe(result); }); }); ``` ### 準備Stub替身 ``` panjiedeMac-Pro:service panjie$ ng g s CourseStub --skip-tests CREATE src/app/service/course-stub.service.ts (139 bytes) ``` service/course-stub.service.ts ```typescript import {Course} from '../norm/entity/course'; import {Observable} from 'rxjs'; export class CourseStubService { constructor() { } save(course: Course): Observable<Course> { return null; } } ``` ## 組件 在前面的章節中,完成了嵌套組件TeacherSelect的測試。除TeacherSelect組件外,課程新增組件中還嵌套了KlassMultiple組件。功能性代碼如下: ### V層 加入班級多選組件 course/add/add.component.html ```html <app-klass-multiple-select (changed)="onKlassesChange($event)"></app-klass-multiple-select> <label><input type="checkbox"> 班級1</label> ? <label><input type="checkbox"> 班級2</label> ? ``` ### C層 實體中加入klasses字段。 norm/entity/course.ts ```typescript export class Course { ... klasses: Klass[]; constructor(data?: { id?: number, name?: string, teacher?: Teacher, klasses?: Klass[]?}) { if (this.teacher) { ? ? if (data.teacher) { ? this.teacher = data.teacher; } if (data.klasses) { this.klasses = data.klasses; } } } ``` * ? 修正一處前面的書寫錯誤。該錯誤是由于沒有對Course進行充分的單元測試造成的 加入班級多選組件對應的onKlassesChange方法。 course/add/add.component.ts ```typescript export class AddComponent implements OnInit { ... klasses: Klass[]; ... onKlassesChange($event: Klass[]) { this.course.klasses = $event; } } ``` ## 嵌套班級多選組件測試 參考上一節中對多選組件的測試方案,在對嵌套班級多選組件測試前先進行一些準備。 ### 新建對應測試模塊 ``` panjiedeMac-Pro:course panjie$ ng g m courseTesting CREATE src/app/course/course-testing/course-testing.module.ts (199 bytes) ``` ### 建立測試用控制器 >[info] 這里的控制器只是個后綴名稱而已,表明此測試模塊中的測試信息可以通過該類獲取到。如果你喜歡其它的名稱,也可以起成其它的名稱。教程中只所以這樣命名,完全是參考的angular官方庫。 ``` panjiedeMac-Pro:course-testing panjie$ ng g class CourseTestingController --skip-tests CREATE src/app/course/course-testing/course-testing-controller.ts (41 bytes) ``` 參考CoreTestingController的代碼,完善功能如下: course/course-testing/course-testing-controller.ts ```typescript export class CourseTestingController { /** * 存儲組件、指令或管道 */ private units = new Array<any>(); constructor() { } /** * 添加單元(組件、指令或管道) * @param unit 單元 */ addUnit(unit: any): void { this.units.push(unit); } /** * 獲取單元(組件、指令或管道) * @param clazz 類型 */ get(clazz: Clazz): any { let result: any = null; this.units.forEach((value) => { if (value.constructor.name === clazz.name) { result = value; } }); return result; } } /** * 定義一個Clazz類型,用于參數中接收 類、接口等 */ type Clazz = new(...args: any[]) => any; ``` ### 建立班級多選組件替身 ``` panjiedeMac-Pro:course-testing panjie$ ng g c KlassMultipleSelect --skip-tests -s? -t? CREATE src/app/course/course-testing/klass-multiple-select/klass-multiple-select.component.ts (299 bytes) UPDATE src/app/course/course-testing/course-testing.module.ts (331 bytes) ``` * ? 不單獨生成sass樣式(style)文件 * ? 不單獨生成html模塊(template)文件 在初始化中將組件本身添加到測試控制器中,以便在單元測試中被獲取; 添加與原組件相同的input與output用與模塊原組件的交互功能。 course/course-testing/klass-multiple-select/klass-multiple-select.component.ts ```typescript import {Component, OnInit} from '@angular/core'; import {CourseTestingController} from '../course-testing-controller'; @Component({ selector: 'app-klass-multiple-select', template: ` <p> klass-multiple-select works! </p> `, styles: [] }) export class KlassMultipleSelectComponent implements OnInit { @Output() changed = new EventEmitter<Klass[]>(); constructor(private controller: CourseTestingController) { this.controller.addUnit(this); } ngOnInit() { } } ``` ### 定制測試模塊 若使KlassMultipleSelectComponent被其它模塊使用,還需要將其添加到exports中。若使CourseTestingController被其它模塊獲取到,則需要將其添加到providers中。 course/course-testing/course-testing.module.ts ```typescript @NgModule({ declarations: [KlassMultipleSelectComponent], imports: [ CommonModule ], exports: [ KlassMultipleSelectComponent ], providers: [ CourseTestingController ] }) export class CourseTestingModule { } ``` ### 測試代碼 course/add/add.component.spec.ts ```typescript imports: [ ReactiveFormsModule, TestModule, CourseTestingModule ? ] providers: [ {provide: CourseService, useClass: CourseStubService} ? ] ... fit('嵌入KlassMultipleSelect組件測試', () => { }); ``` 完成測試功能: course/add/add.component.spec.ts ```typescript fit('嵌入KlassMultipleSelect組件測試', () => { const courseTestController: CourseTestingController = TestBed.get(CourseTestingController); const klassMultipleSelectComponent: KlassMultipleSelectComponent = courseTestController.get(KlassMultipleSelectComponent); spyOn(component, 'onKlassesChange'); const klasses = [new Klass(null, null, null)]; klassMultipleSelectComponent.changed.emit(klasses); expect(component.onKlassesChange).toHaveBeenCalledWith(klasses); }); ``` ## 補充其它功能單元測試 更新onSubmit方法 course/add/add.component.ts ```typescript constructor(private formBuilder: FormBuilder, private courseService: CourseService?) { } onSubmit() { this.courseService.save(this.course).subscribe((course) => { console.log(course); }); } ``` ### 單元測試 course/add/add.component.spec.ts ```typescript fit('ngOnInit', () => { }); fit('onTeacherSelect', () => { }); fit('onKlassesChange', () => { }); fit('onSubmit', () => { }); ``` 補充功能代碼后如下: course/add/add.component.spec.ts ```typescript /** * 在beforeEach的組件初始化代碼中。 * 當fixture.detectChanges();被首次執行時,會自動執行一次ngOnInit方法 */ fit('ngOnInit', () => { expect(component.formGroup).toBeDefined(); expect(component.course).toBeDefined(); }); fit('onTeacherSelect', () => { const teacher = new Teacher(null, null, null); component.onTeacherSelect(teacher); expect(component.course.teacher).toBe(teacher); }); fit('onKlassesChange', () => { const klasses = [new Klass(null, null, null)]; component.onKlassesChange(klasses); expect(component.course.klasses).toBe(klasses); }); fit('onSubmit', () => { const course = new Course(); component.course = course; const courseService: CourseService = TestBed.get(CourseService); const returnCourse = new Course(); spyOn(courseService, 'save').and.returnValue(of(returnCourse)); spyOn(console, 'log'); component.onSubmit(); expect(courseService.save).toHaveBeenCalledWith(course); expect(console.log).toHaveBeenCalledWith(returnCourse); }); ``` 至此,使用已學習過的知識完成了新增課程的前臺基本功能。 # 單元測試 將所有的`fit`變成`it`,所有的`fdescribe`變成`describe`后對項目整體運行單元測試,以保障整個項目均是符合預期的。 ![](https://img.kancloud.cn/06/ab/06abcbb47d64392b0d475100142e4ebd_796x78.png) 提示在班級選擇組件中找不到TeacherSelectService的提供者,這是由于對TeacherSelect的替身組件進行了升級:在該組件中裝入了一對一的服務造成的。解決的方法有兩種:1. 直接使用provide的方法在測試中提供該一對一服務。 2. 引入該一對一服務所在的模塊。 經過查找最終發現該測試的位置竟然位于測試模塊下: ![](https://img.kancloud.cn/8f/db/8fdb1d4706b52cdd0cefc941b4604836_501x305.png) 這是由于在生成替身組件時沒有跳過測試文件造成的,為此將其刪除即可。刪除后重新執行單元測試: ![](https://img.kancloud.cn/c4/29/c429c73836e9cead8729a3b3012a9d91_1177x125.png) # 總結 本節完成的功能性代碼主要有三點:1.完成了M層的開發。 2.在C層中嵌套班級多選組件。 3.完成了課程新增組件的功能開發。大部分的精力與時間都在組織單元測試上。這初步看起來好像浪費了很長的時間,其實不盡然。在生產環境中,不可能整個項目都是簡簡單單的CURD,也不可能完全是由你一個人完成的。單元測試可以保障在前后臺分離的項目中,不依賴于后臺接口的完成度而進行獨立的開發;單元測試也可以保障在前臺其它所依賴的模塊未完成時進行本模塊的開發。以當前課程新增功能為例:后臺尚未啟動,但前臺已經完成了功能性的開發,這證明了前臺是可以脫離后的支持而獨力開發的。 其實還有一種開發思路。仍以當前課程新增功能為例。當前功能依賴于班級多選組件,在開發流程上,我們習慣性的先開發了班級多選組件,而后在此基礎上完成了當前課程新增功能。其實在單元測試的支持下,完全不必先開發班級多選組件,而只需要規定班級多選組件的接口(input\output),然后按此接口初始化班級多選組件的替身即可。這點可以使用刪除當前已完成的班級多選組件來驗證:班級多選組件被刪除后,我們僅需要于CourseModule中刪除對其的聲明,如果在單元測試中不小心引用了該組件,僅需要將引用改為該組件的替身。班級多選組件被刪除后,當前課程新增的所有功能同樣可以通過單元測試。 # 參考文檔 | 名稱 | 鏈接 | 預計學習時長(分) | | --- | --- | --- | | 源碼地址 | [https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step6.1.5](https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step6.1.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>

                              哎呀哎呀视频在线观看