ionic 單元測試

Angular項(xiàng)目創(chuàng)建時(shí),會(huì)自動(dòng)生成spec.ts單元測試文件. 在ionic項(xiàng)目中, 需要手動(dòng)創(chuàng)建spec.ts文件和添加依賴的框架.

添加配置依賴可以參看ionic-team寫的一篇文章 :點(diǎn)我??

單元測試基礎(chǔ)入門

簡單示例
describe('A suite is just function',()=>{
  var a;
  it('and so is a spec ', function () {
    a = true;
    expect(a).toBe(true);
  });
})

執(zhí)行結(jié)果:


a 等于 true, 用例測試成功

上面的例子是一個(gè)很簡單的單元測試, 它有三部分組成: describe() , it()expect()組成;

編寫單元測試用例的基本步驟:
  • 設(shè)計(jì)測試用例函數(shù): 根據(jù)測試場景組合使用describe()it()來定義測試用例;
  • 編寫判斷邏輯: 在it()函數(shù)中, 需要使用expect()斷言函數(shù)來判定用例測試結(jié)果. 例如, expect(a).toBe(true);表示變量a應(yīng)等于true,否則說明這個(gè)用例測試失敗.
    a 等于 false, 則用例測試失敗
測試集

Jasmine中, 將功能相似的測試用例統(tǒng)一聚集在測試集(Suites)中, 并通過describe()函數(shù)標(biāo)識(shí). 假設(shè)被測試的業(yè)務(wù)類有多個(gè)函數(shù), 則需要編寫多個(gè)測試用例, 并將這些測試用例都放在同個(gè) describe()函數(shù)中.

測試點(diǎn)

每個(gè)具體的功能測試點(diǎn) (Spec) 可以用全局函數(shù)it()來定義, 該函數(shù)的第一個(gè)參數(shù)表示該用例的名稱, 第二個(gè)參數(shù)為測試的細(xì)節(jié). it()函數(shù)可以包含一個(gè)或者多個(gè)斷言 (expect), 每個(gè)斷言的結(jié)果只能是 true或者false, 只有當(dāng)所有斷言都為true是, 該測試點(diǎn)才算通過.

內(nèi)置匹配器

內(nèi)置匹配器(Matchers), 就是更在expect()函數(shù)后面, 用于判斷結(jié)果是否符合期望值的函數(shù). 在上面的例子中, 已經(jīng)使用了一個(gè)很常用的匹配器toBe().

內(nèi)置常用匹配器

  • toBe()

toBe()本質(zhì)是使用操作符 ===來比較結(jié)果值和期望值, not.toBe()則表示不等, 示例代碼如下:

  it("The 'toBe()' matcher compares with '===' ", function () {
    var a = 15;
    var b = a;
    expect(a).toBe(b);
    expect(a).not.toBe(null);
  });
  • toEqual()
    toEqual()用于比較兩個(gè)對象、數(shù)字、字符串等, 示例代碼如下:
  it('should work for objects', function () {
    var foo = {
      a: 8,
      b: 15
    };

    var bar = {
      a: 8,
      b: 15
    }

    expect(foo).toEqual(bar);
  });
  • toMatch()
    toMatch()用于驗(yàn)證是否匹配正則表達(dá)式, 示例代碼如下:
  it("The 'toMatch' matcher is for regular expressions", function () {
    var message = 'Ionic CLI and Cordova';
    expect(message).toMatch(/ionic/i);
  });
  • toContain()
    toContain()用于驗(yàn)證數(shù)組是否包含指定元素, 示例代碼如下:
  it("The 'toContain' matcher is for finding an item in an array", function () {
    var array = ['React', 'Vue', 'Angular'];
    expect(array).toContain('Angular');
  });
Jasmine 內(nèi)置匹配器:
匹配器 作用
toBe 使用 === 比較結(jié)果
toEqual 比較兩個(gè)對象、數(shù)字、字符串等是否相等
toMatch 正則表達(dá)式匹配
toBeNull 驗(yàn)證是否為null
toBeTruthy 驗(yàn)證是否為 true
toBeFalsy 驗(yàn)證是否為 false
toBeLessThan 驗(yàn)證結(jié)果是否小于指定值
toBeGreaterThan 驗(yàn)證結(jié)果是否大于指定值
toContain 驗(yàn)證數(shù)組是否包含指定元素
toBeCloseTo 將值進(jìn)行四舍五入后比較是否相等
toThrow 驗(yàn)證函數(shù)是否拋出一個(gè)錯(cuò)誤
toThrowError 驗(yàn)證函數(shù)是否拋出指定的錯(cuò)誤
toBeDefined 驗(yàn)證對象是否 不為 undefined
beforeEach 、afterEach函數(shù)

另外, 在測試中還會(huì)經(jīng)常用到 beforeEach() 、afterEach() 這兩個(gè)函數(shù), 用于定義每個(gè)測試用例執(zhí)行前及執(zhí)行后的公共邏輯 。

  • beforeEach() : 定義了每個(gè)用例在執(zhí)行前, 所需要執(zhí)行的初始化函數(shù)。
  • afterEach (): 每個(gè)用例在執(zhí)行結(jié)束后, 均將執(zhí)行afterEach ()函數(shù)。

beforeEach()的簡單使用:

describe('How to use beforeEach', () => {

  let foo, bar;
  beforeEach(() => {
    foo = {
      a: 8,
      b: 15
    };

    bar = {
      a: 8,
      b: 15
    }
  })

  it('should work for objects', function () {
    expect(foo).toEqual(bar);
  });
})

單元測試

  • 組件的測試
    page3.html:
    <ion-content no-padding>
      <ion-list>
        <button ion-item *ngFor="let item of list">
          {{item}}
        </button>
      </ion-list>
    </ion-content>
    
    page3.ts:
    export class Page3 {
    
      list: string[] = ['張三', '李四', '王五'];
    
      constructor() {}
      ionViewDidLoad() {}
    
      addItem() {
        this.list.push(`趙六`);
      }
    }
    
    page3.spec.ts
    import {Page3} from "./page3";
    
    describe('Page3 組件', () => {
      let component;
      beforeEach(() => {
        component = new Page3();
      });
    
      it("調(diào)用 addItem() 函數(shù), 并檢測該函數(shù)是否添加了 '趙六'", function() {
        component.addItem();
        expect(component.list).toContain('趙六');
      });
    })
    
  • 管道的測試
    要求: 創(chuàng)建一個(gè)pipe, 把手機(jī)號(hào)(15912345678)顯示為159-1234-5678的形式,并對該pipe做單元測試。
    ll-phone.ts
    import {Pipe, PipeTransform} from '@angular/core';
    
    @Pipe({
      name: 'llPhone',
    })
    export class LlPhonePipe implements PipeTransform {
    
      transform(value: string): string {
        if (!value) return '';
        if (value.length === 11) {
          return value.replace(/(\d{3})(\d{4})(\d{4})/, (m, m1, m2, m3) =>     {
            return [m1, m2, m3].join('-');
          })
        } else {
          return value;
        }
      }
    }
    
    ll-phone.spec.ts:
    import {LlPhonePipe} from "./ll-phone";
    
    describe('測試 llPhonePipe', () => {
    
      let phonePipe: LlPhonePipe;
    
      beforeEach(() => {
        phonePipe = new LlPhonePipe();
      });
    
      it('should transforms phone', function () {
        expect(phonePipe.transform('15912345678')).toEqual('159-1234-5678');
      });
    })
    
  • 服務(wù)的測試
    創(chuàng)建一個(gè)MockDataLlMockDataProvider
    ll-mock-data.ts:
    import {Injectable} from '@angular/core';
    
    @Injectable()
    export class LlMockDataProvider {
      constructor() {}
      public hotMenu = [{
        "img": "http://file.j1home.com/%E5%AE%89%E5%85%A8%E7%A4%BE%E5%8C%BA@2x.png",
        "name": "社區(qū)安全"
      }, {
        "img": "http://file.j1home.com/%E7%A4%BE%E5%8C%BA%E5%85%9A%E5%BB%BA@2x.png",
        "name": "社區(qū)黨建"
      }];
    }
    
    ll-mock-data.spec.ts:
    import {inject, TestBed} from "@angular/core/testing";
    import {LlMockDataProvider} from "./ll-mock-data";
    
    describe('test provider', () => {
      beforeEach(() => {
        TestBed.configureTestingModule({
          providers: [LlMockDataProvider]
        });
      });
    
      it('should contain item', inject([LlMockDataProvider], (service) => {
        expect(service.hotMenu[1]['name']).toEqual('社區(qū)黨建');
      }));
    })
    

通過TestBed注入被測類

在上面的代碼ll-mock-data.spec.ts中,我們使用到了TestBed.configureTestingModule(),它為我們創(chuàng)建了一個(gè)測試 NgModule,在它的元數(shù)據(jù)里面,我們可以注入被測試的類。如果有依賴,比如:Http,那么你需要 imports: [HttpModule],這一切都跟 NgModule 寫法完全一置。

beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [LlMockDataProvider]
    });
});

組件 DOM 交互測試

在下面的圖片中, 如果想檢測DOM元素的內(nèi)容是否符合我們的要求,
可通過TestBed進(jìn)行組件的初始化, 可以獲取組件模板對應(yīng)的DOM元素以及檢查 DOM元素的內(nèi)容等。

示例代碼如下:
page3.html

<ion-content no-padding>
  <ion-list>
    <button ion-item *ngFor="let item of list">
      {{item | llPhone}}
    </button>
  </ion-list>
</ion-content>

page3.ts

import {Component} from '@angular/core';
import {IonicPage, NavController, NavParams} from 'ionic-angular';

@IonicPage()
@Component({
  selector: 'page-page3',
  templateUrl: 'page3.html',
})
export class Page3 {
  list: string[] = ['15512345678'];
  constructor() {}
  ionViewDidLoad() {}
  addItem() {
    this.list.push(`15512345678`);
  }
}

page3.spec.ts

import {TestBed} from "@angular/core/testing";
import {Page3} from "./page3";
import {IonicModule} from "ionic-angular";
import {LlPhonePipe} from "../../pipes/ll-phone/ll-phone";

describe('Page3 組件', () => {

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [Page3, LlPhonePipe],
      imports: [
        IonicModule.forRoot(Page3)
      ],
    });
  });

  it('test simple component with TestComponentBuilder', () => {
    var fixture = TestBed.createComponent(Page3);
    var compiled = fixture.debugElement.nativeElement;

    // 調(diào)用 組件的 addItem() 函數(shù)
    fixture.componentInstance.addItem();
    // 變化監(jiān)測
    fixture.detectChanges();

    // replace(/(^\s*)|(\s*$)/g, "") 移除字符串首尾空格
    let val = compiled.querySelector('.label').textContent.replace(/(^\s*)|(\s*$)/g, "");
    expect(val).toBe('155-1234-5678');
  });
});

在上面的代碼中, 需要關(guān)注以下的知識(shí)點(diǎn):

  • TestBedcreateComponent()方法用于初始化被測組件, 并創(chuàng)建 ComponentFixture對象, 這個(gè)對象可以認(rèn)為是被測組件的上下文環(huán)境, 通過它可以獲取已經(jīng)完成初始化的組件實(shí)例以及DOM元素等。
  • fixture.detectChanges()方法用于當(dāng)JavaScript變量及模板內(nèi)容變更時(shí)觸發(fā)變化監(jiān)測。組件初始化、DOM元素或JavaScript值變化時(shí),都需要調(diào)用這個(gè)方法, 以便觸發(fā) Angular的變化監(jiān)測機(jī)制。
  • fixture.debugElement.nativeElement用于獲取組件對應(yīng)的原生DOM元素, 之后可以通過querySelector()DOM元素原生 API做進(jìn)一步處理。
  • fixture.componentInstance用于獲取組件對應(yīng)的對象實(shí)例,上面代碼中通過fixture.componentInstance調(diào)用該實(shí)例對象的addItem()函數(shù): fixture.componentInstance.addItem();
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 這三天我分別寫了關(guān)于未來職業(yè)的發(fā)展趨勢,看完文章之后,大家可以重新思考自己的底層工作方式。 三個(gè)維度提升你的職業(yè)身...
    123涅槃閱讀 505評論 14 8
  • 終日不鍛煉的同學(xué),估計(jì)很難體會(huì)到,健身人士的那些“不良癖好”,健身人士也是要上班工作的,只有少數(shù)的人拿健身當(dāng)做工作...
    九魚亭閱讀 1,944評論 0 0
  • 張遠(yuǎn)兮本想著開年了,應(yīng)該會(huì)轉(zhuǎn)轉(zhuǎn)運(yùn)吧,即使不轉(zhuǎn)運(yùn)總會(huì)有點(diǎn)新氣象。結(jié)果人生不如意不是八九,而是十打十的十。 一早上起來...
    楊耿耿閱讀 202評論 0 0

友情鏈接更多精彩內(nèi)容