Angular入門:簡述單元測試排錯

零、前言

學習Angular的時候,總感受特別的複雜、高級,以致於產生畏懼心理,這種心理尤爲體如今單元測試上。webpack

今天被醍醐灌頂以後,感受單元測試的報錯信息也不是那麼難看懂了。web

(文章的後半段是記錄我本身的一次單元測試的過程,爲了避免耽誤讀者時間,我把結論寫在第一小節。)緩存

1、分析報錯信息

啓動單元測試後,映入眼簾的是一堆信息:app

圖片.png

先來看頂部的信息:async

圖片.png

最上面的一排點···············是整個項目的測試總數,下面的Ran 1 of 58 specs是一共58個測試,本次測試啓動了一個。ide

下面的1 spec, 1 failure是本次啓動的一個測試中,有一個出錯了,而後列出了全部報錯的測試項,以及錯誤信息。單元測試

而後看錯誤信息:學習

圖片.png

頂上的 TypeError: Cannot set property 'workId' of undefined 是主要的錯誤類型。測試

本文舉例的錯誤是類型錯誤,不能對一個 undefined 對象調用 workId 屬性。this

下面的那一大片代碼,是方法的堆棧
舉個例子:若是 A( )調用了 B( ), B( ) 調用了C( ),那麼堆棧狀況就是這樣:

圖片.png

咱們知道,棧是先進後出,隊列是先進先出,這裏使用的就是棧。

  • 當A( )方法被調用時,內存加載A( ),此時A就在棧上
  • 執行的時候忽然發現,A( )須要調用B( )方法,此時應該加載B( )
  • 但A( )尚未執行完畢,不能釋放,因此就用棧的方式,把A( )壓在下面而且凍結
  • B( )的執行過程當中又須要C( ),因此B凍結,C入棧。
  • 等到C( )執行完以後,把結果返回給B( ),此時C( )被釋放,出棧
  • B( )被解凍,拿到C( )的返回值,繼續執行後面的語句
  • 後面的過程同理......

因此,這張圖的意思,就是堆棧,最上面的方法,是當前的活躍方法,也就是出錯的方法:

圖片.png

能夠清晰的看到at WorkStubService.getById (http://localhost:9877/_karma_webpack_/src/app/service/service-tesing/work-stub.service.ts:76:32)

翻譯一下就是,錯誤發生在WorkStubService類的getById方法,連接的最後顯示了,是work-stub.service.ts文件的第76行的第32個字符,出現了問題。

而後定位到這行代碼,就能夠清楚的找到問題了。

分析報錯信息的方法介紹,到此結束。

2、記一次排錯實錄

計劃測試EditComponent組件的C層初始化。

C層:

export class EditComponent implements OnInit {

  work = new Work();
  params = {
    workId: 0
  };
  constructor(private workService: WorkService) {
  }

  ngOnInit() {
    this.params.workId = 0;
    this.load();
  }


  public load() {
    this.workService.getById({id: this.params.workId})
      .subscribe((data) => {
        this.work = data;
        console.log(this.work);
      }, () => {
        console.log('error');
      });
  }
  
}

ngOninit調用load方法,load調用service來獲取數據,

因此測試的思路就是讓組件初始化,而後斷言C層向M層的傳值,以及斷言M層的返回值便可。

describe('Page -> Teacher -> EditComponent', () => {
  let component: EditComponent;
  let fixture: ComponentFixture<EditComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ EditComponent ],
      imports: [
        HttpClientTestingModule,
      ],
      providers: [
        {provide: WorkService, useClass: WorkStubService}
      ]
    })
      .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(EditComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });


  it('組件初始化發起請求測試', () => {
    /* 獲取請求參數 */
    const workService: WorkStubService = TestBed.get(WorkService);
    const queryParam = workService.pageParamsCache;

    /* 斷言傳入的參數值與組件中的參數值相同 */
    expect(queryParam.workId).toEqual(component.params.workId);
  });


  it('組件初始化V層渲染', () => {
    /* 斷言總行數及第一行的內容綁定符合預期 */

    expect(component.work.id).toBe(1);
    expect(component.work.content).toBe('<p>content</p>');
    expect(component.work.item).toBe(new Item({name: 'Item'}));
    expect(component.work.score).toBe(100);
    expect(component.work.student).toBe(new Student({name: 'Student'}));
    expect(component.work.reviewed).toBe(true);

  });
});

一共三個小的測試項。爲了模擬返回值,還須要一個假的M層。

export class WorkStubService {

  constructor() {
  }

  /* 傳入參數緩存 */
  pageParamsCache: { page: number, size: number ,workId: number};

  /**
   * getById模擬方法
   * @param params 查詢參數
   */
  getById(params: { id: number }): Observable<Work> {
    this.pageParamsCache.workId = params.id;
    const mockResult = new Work(
      {
            id: 1, content: '<p>content</p>', item: new Item({name: 'Item'}),
            score: 100, student: new Student({name: 'Student'}), reviewed: true});
    return of(mockResult);
  }
}

而後一運行測試,我懵了,全部模擬的返回值,全都是undefined:
圖片.png

帶着疑惑的心態,開始看本身的代碼,感受哪裏都沒問題...
更況且這些代碼是從老項目裏面粘過來的,「不可能」出錯。

爲了找出不一樣,我把代碼和老項目一行一行的對照,的確沒有問題。

而後我發現了模擬M層的getById方法是灰色,還執拗的認爲多是沒有被調用:
圖片.png

我又打了一堆斷點,事實證實,即便是灰色,也確實被執行了。
圖片.png

直到這時,我尚未仔細的看報錯信息。

終於,沒法本身解決了,只能問老師,而後才發現,關鍵在於報了一個TypeError: Cannot set property 'workId' of undefined
圖片.png

一看,undefined,因而又傻傻的去C層找定義變量的代碼,
圖片.png
都有初始值,怎麼會是undefined呢??

後來老師又提示了,看報錯信息。才知道那一大堆信息,其實是堆棧,若是要找到具體的出錯位置,只須要看最上面的一行,就能夠了。
at WorkStubService.getById (http://localhost:9876/_karma_webpack_/src/app/service/service-tesing/work-stub.service.ts:76:32)

定位到代碼,是模擬服務層的這一行出現了問題:
圖片.png

這個變量是這樣定義的,換言之,只定義了類型,並無初始化:
圖片.png

因此,在undefined對象上調用wordId,固然會出錯了。我恍然大悟。

而後另外一個疑惑產生了,個人代碼是粘過來的,爲何在以前的項目裏沒報錯呢?
仔細的看了老項目,才發現個人錯誤源自於一個細微的改動。

老項目是這麼寫的:
圖片.png

this.pageParamsCache = params;

把全部的參數直接賦值給pageParamsCache,不存在對它調用屬性的問題。
而我爲了方便,改爲了:

this.pageParamsCache.workId = params.id;

這一改,就把本來的總體賦值,變成了賦值單個屬性,可是pageParamsCache並無初始化,因此纔出現了undefined錯誤。

總結

不管初學什麼技能,必定不要好高騖遠,也不能自做主張,最重要的是遇到問題不要瞎猜,要按照合理的思惟方式來思考。

青銅玩家左思右想半天都沒有解決的問題,對於王者段位來講,就是兩句話的事。這就是青銅和王者的差距。

爲何青銅玩家解決不了呢?由於我一開始的思考方向就錯了。

相關文章
相關標籤/搜索